A simple C library for compressing lists of integers using binary packing

Overview

The SIMDComp library

Build Status Build Status Code Quality: Cpp

A simple C library for compressing lists of integers using binary packing and SIMD instructions. The assumption is either that you have a list of 32-bit integers where most of them are small, or a list of 32-bit integers where differences between successive integers are small. No software is able to reliably compress an array of 32-bit random numbers.

This library can decode at least 4 billions of compressed integers per second on most desktop or laptop processors. That is, it can decompress data at a rate of 15 GB/s. This is significantly faster than generic codecs like gzip, LZO, Snappy or LZ4.

On a Skylake Intel processor, it can decode integers at a rate 0.3 cycles per integer, which can easily translate into more than 8 decoded billions integers per second.

This library is part of the Awesome C list of C ressources.

Contributors: Daniel Lemire, Nathan Kurz, Christoph Rupp, Anatol Belski, Nick White and others

What is it for?

This is a low-level library for fast integer compression. By design it does not define a compressed format. It is up to the (sophisticated) user to create a compressed format.

It is used by:

Requirements

  • Your processor should support SSE4.1 (It is supported by most Intel and AMD processors released since 2008.)
  • It is possible to build the core part of the code if your processor support SSE2 (Pentium4 or better)
  • C99 compliant compiler (GCC is assumed)
  • A Linux-like distribution is assumed by the makefile

For a plain C version that does not use SIMD instructions, see https://github.com/lemire/LittleIntPacker

Usage

Compression works over blocks of 128 integers.

For a complete working example, see example.c (you can build it and run it with "make example; ./example").

  1. Lists of integers in random order.
const uint32_t b = maxbits(datain);// computes bit width
simdpackwithoutmask(datain, buffer, b);//compressed to buffer, compressing 128 32-bit integers down to b*32 bytes
simdunpack(buffer, backbuffer, b);//uncompressed to backbuffer

While 128 32-bit integers are read, only b 128-bit words are written. Thus, the compression ratio is 32/b.

  1. Sorted lists of integers.

We used differential coding: we store the difference between successive integers. For this purpose, we need an initial value (called offset).

uint32_t offset = 0;
uint32_t b1 = simdmaxbitsd1(offset,datain); // bit width
simdpackwithoutmaskd1(offset, datain, buffer, b1);//compressing 128 32-bit integers down to b1*32 bytes
simdunpackd1(offset, buffer, backbuffer, b1);//uncompressed

General example for arrays of arbitrary length:

int compress_decompress_demo() {
  size_t k, N = 9999;
  __m128i * endofbuf;
  uint32_t * datain = malloc(N * sizeof(uint32_t));
  uint8_t * buffer;
  uint32_t * backbuffer = malloc(N * sizeof(uint32_t));
  uint32_t b;

  for (k = 0; k < N; ++k){        /* start with k=0, not k=1! */
    datain[k] = k;
  }

  b = maxbits_length(datain, N);
  buffer = malloc(simdpack_compressedbytes(N,b)); // allocate just enough memory
  endofbuf = simdpack_length(datain, N, (__m128i *)buffer, b);
  /* compressed data is stored between buffer and endofbuf using (endofbuf-buffer)*sizeof(__m128i) bytes */
  /* would be safe to do : buffer = realloc(buffer,(endofbuf-(__m128i *)buffer)*sizeof(__m128i)); */
  simdunpack_length((const __m128i *)buffer, N, backbuffer, b);

  for (k = 0; k < N; ++k){
    if(datain[k] != backbuffer[k]) {
      printf("bug\n");
      return -1;
    }
  }
  return 0;
}
  1. Frame-of-Reference

We also have frame-of-reference (FOR) functions (see simdfor.h header). They work like the bit packing routines, but do not use differential coding so they allow faster search in some cases, at the expense of compression.

Setup

make make test

and if you are daring:

make install

Go

If you are a go user, there is a "go" folder where you will find a simple demo.

Other libraries

Other programming languages

References

Issues
  • SSE2 compatibility should be warranted

    SSE2 compatibility should be warranted

    See the actual discussion here https://github.com/lemire/simdcomp/commit/1a6ea48fb9ba53888470e891b321164ab61ecb39, but in short - while various SSE versions have useful features, SSE2 is the standard set. The suggestion is to retain the compatibility with SSE2 even while some features from the upper SSE versions are used.

    This is done by the following steps

    • retaining the emulation layer for the features that aren't available with SSE2
    • provide a legacy option to the makefiles, so then a fully SSE2 compatible version of the library can be compiled. This shouldn't involve any run time feature replacement (by usage of cpuid, etc).
    enhancement 
    opened by weltling 21
  • /GS for release Windows builds

    /GS for release Windows builds

    The Windows makefile enables /GS (buffer security checks) for release builds. I won't argue in favor of security over performance or the other way around, but is this intentional or should it be /GS-?

    opened by lnicola 16
  • simdunpack doesn't decompress the values properly

    simdunpack doesn't decompress the values properly

    Hi

    Here's a simple piece of code to reproduce the issue:

    size_t N = 9999;
    uint32_t * datain = malloc(N * sizeof(uint32_t));
    uint8_t * buffer = malloc(N * sizeof(uint32_t) + N / SIMDBlockSize);
    uint32_t * backbuffer = malloc(N * sizeof(uint32_t));
    
    for (k = 1; k < N; ++k){       
        datain[k] = k;
    }
    
    const uint32_t b = maxbits(datain);
    
    simdpackwithoutmask(datain, buffer, b);
    
    simdunpack(buffer, backbuffer, b);
    
    for (k = 1; k < N; ++k){       
        printf ("%d\n", backbuffer[k]);
    }
    

    Not quiet sure if this is an issue with the buffer that I'm initializing, basically i need to compress an array of random Integers.

    opened by akhld 12
  • How to get the usable bytes from a compressed array?

    How to get the usable bytes from a compressed array?

    I understand I have to malloc enough to fit in the compressed array, but how do I retrieve the usable information? Sorry that I'm not a sophisticated user.

    opened by wshager 11
  • Support signed differential coding

    Support signed differential coding

    Would you have a recommendation for data like a price tick stream where the values are uint32 and there are small deltas but are unsorted? The next price should be a small delta from the previous but it would be signed.

    opened by AnthonyLloyd 9
  • simdcomp vs roaringBitmap

    simdcomp vs roaringBitmap

    @lemire , thanks a lot for these wonderful libraries.

    Just being curious about one thing here, how do these two libraries (simdcomp vs roaringBitmap) stand against each other in terms of encoding/decoding speed and compression performances when it comes to ordered numbers?

    Though i have used roaringBitmaps, never benchmarked/verified the compression ratio there.. But while glancing over this simdcomp, end up running the go/test and realised that the compression ratio was ~32.

    Another side note, is it would be great to have a sample for the variable length array encoding/decoding for the Go users in simdcomp.

    thanks!

    opened by sreekanth-cb 6
  • How to compress/decompress sorted array of arbitrary length?

    How to compress/decompress sorted array of arbitrary length?

    I didn't manage to create a function to successfully decompress a sorted array of arbitrary length. How to do this? I tried several things, including adapting the simdpack_length/simdunpack_length functions to simply use simdpackd1/simdunpackd1. Is this not correct? The function works correctly for 128 integers.

    opened by wshager 6
  • Packing 2 bits values into a bigger

    Packing 2 bits values into a bigger

    Hi @lemire,

    Thank you for all your work and all the resources you had provided.

    Is there any reference to efficiently pack the 2 bits into a larger data type(basically int64)?

    A = 0000.........10(64 bit), B = 0000........11(64 bit), C = 0000........01, and so on, these are int64 values containing 2-bit values.

    So, I want to pack them in an int64 datatype(32 2-bit values) Z = 101101...........(int64 value, containing packed values of A, B, C, D, .............. )

    I want to perform this efficiently. So It would be helpful if you can help me with some references.

    Thanks in advance!

    opened by Darshvino 5
  • C89 compatibility

    C89 compatibility

    @lemire i think it's finished now also with the strict gcc C89 compat. But actually I'm kind of disappointed with that as the source looks much uglier now, just as you said :) ... Here's the diff https://github.com/lemire/simdcomp/compare/c89_compat . The unit and example progs seem to pass however, also no any significant signs in the performance.

    Not sure it should be merged, nevertheless one could keep that branch for people who possibly need C89 compatible code for whatever reasons (mostly compiler bound probably). Or, one could merge it but omit the gcc commit. That were a sort of broken C89 compatibility where the C source would partly follow it, but still would be able to use stdint.h, the always_inline attribute and maybe more. To note is also that gcc and vc are not the only compilers around.

    I'm thinking about the real world implementation and particularly about implementing some PHP extension. There one is not only bound to the just compressing data, but can also implementing data stream compression, data serialization, beyond. However would probably reuse the C89 branch for that, as mentioned earlier, PHP sticks to C89 (but more like in that broken C89 variant ATM).

    Thanks.

    enhancement 
    opened by weltling 5
  • heap-buffer-overflow (detected by LibFuzzer)

    heap-buffer-overflow (detected by LibFuzzer)

    Here is my function LLVMFuzzerTestOneInput : test_input.zip

    sizeof in is :  110
    0 : 73
    1 : 73
    2 : 73
    3 : 73
    4 : 73
    5 : 73
    6 : 157
    7 : 73
    8 : 73
    9 : 73
    10 : 73
    11 : 73
    12 : 73
    13 : 73
    14 : 0
    15 : 0
    16 : 0
    17 : 0
    18 : 0
    19 : 0
    20 : 0
    21 : 0
    22 : 0
    23 : 0
    24 : 0
    25 : 0
    26 : 0
    27 : 0
    28 : 0
    29 : 0
    30 : 0
    31 : 0
    32 : 0
    33 : 0
    34 : 0
    35 : 0
    36 : 0
    37 : 0
    38 : 0
    39 : 0
    40 : 0
    41 : 0
    42 : 255
    43 : 255
    44 : 255
    45 : 0
    46 : 0
    47 : 0
    48 : 0
    49 : 0
    50 : 0
    51 : 0
    52 : 0
    53 : 0
    54 : 0
    55 : 0
    56 : 0
    57 : 0
    58 : 0
    59 : 0
    60 : 0
    61 : 0
    62 : 0
    63 : 0
    64 : 0
    65 : 0
    66 : 0
    67 : 0
    68 : 0
    69 : 0
    70 : 0
    71 : 0
    72 : 0
    73 : 0
    74 : 0
    75 : 0
    76 : 0
    77 : 0
    78 : 0
    79 : 0
    80 : 0
    81 : 0
    82 : 0
    83 : 0
    84 : 0
    85 : 0
    86 : 0
    87 : 0
    88 : 0
    89 : 0
    90 : 0
    91 : 0
    92 : 0
    93 : 0
    94 : 0
    95 : 41
    96 : 0
    97 : 0
    98 : 0
    99 : 0
    100 : 0
    101 : 0
    102 : 0
    103 : 73
    104 : 73
    105 : 73
    106 : 73
    107 : 73
    108 : 73
    109 : 41
    packing
    unpacking
    =================================================================
    ==2137==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60b000001600 at pc 0x00000068a431 bp 0x7ffe5a401c90 sp 0x7ffe5a401c88
    READ of size 16 at 0x60b000001600 thread T0
        #0 0x68a430  (/my/simdcomp/test_input+0x68a430)
        #1 0x54ab28  (/my/simdcomp/test_input+0x54ab28)
        #2 0x42eac7  (/my/simdcomp/test_input+0x42eac7)
        #3 0x439334  (/my/simdcomp/test_input+0x439334)
        #4 0x43a99f  (/my/simdcomp/test_input+0x43a99f)
        #5 0x429d5c  (/my/simdcomp/test_input+0x429d5c)
        #6 0x41cc22  (/my/simdcomp/test_input+0x41cc22)
        #7 0x7f1c2cf39b96  (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
        #8 0x41cc99  (/my/simdcomp/test_input+0x41cc99)
    
    0x60b000001600 is located 0 bytes to the right of 112-byte region [0x60b000001590,0x60b000001600)
    allocated by thread T0 here:
        #0 0x5121b0  (/my/simdcomp/test_input+0x5121b0)
        #1 0x54aaeb  (/my/simdcomp/test_input+0x54aaeb)
        #2 0x42eac7  (/my/simdcomp/test_input+0x42eac7)
        #3 0x439334  (/my/simdcomp/test_input+0x439334)
        #4 0x43a99f  (/my/simdcomp/test_input+0x43a99f)
        #5 0x429d5c  (/my/simdcomp/test_input+0x429d5c)
        #6 0x41cc22  (/my/simdcomp/test_input+0x41cc22)
        #7 0x7f1c2cf39b96  (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    
    SUMMARY: AddressSanitizer: heap-buffer-overflow (/my/simdcomp/test_input+0x68a430) 
    Shadow bytes around the buggy address:
      0x0c167fff8270: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
      0x0c167fff8280: fa fa fa fa fa fa fd fd fd fd fd fd fd fd fd fd
      0x0c167fff8290: fd fd fd fd fa fa fa fa fa fa fa fa 00 00 00 00
      0x0c167fff82a0: 00 00 00 00 00 00 00 00 00 06 fa fa fa fa fa fa
      0x0c167fff82b0: fa fa 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    =>0x0c167fff82c0:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x0c167fff82d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x0c167fff82e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x0c167fff82f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x0c167fff8300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      0x0c167fff8310: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    Shadow byte legend (one shadow byte represents 8 application bytes):
      Addressable:           00
      Partially addressable: 01 02 03 04 05 06 07 
      Heap left redzone:       fa
      Freed heap region:       fd
      Stack left redzone:      f1
      Stack mid redzone:       f2
      Stack right redzone:     f3
      Stack after return:      f5
      Stack use after scope:   f8
      Global redzone:          f9
      Global init order:       f6
      Poisoned by user:        f7
      Container overflow:      fc
      Array cookie:            ac
      Intra object redzone:    bb
      ASan internal:           fe
      Left alloca redzone:     ca
      Right alloca redzone:    cb
    ==2137==ABORTING
    MS: 2 InsertByte-InsertRepeatedBytes-; base unit: e327b8bb508e837ff0f4550c4494658769d8262a
    0x49,0x49,0x49,0x49,0x49,0x49,0x9d,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x29,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x49,0x49,0x49,0x49,0x49,0x49,0x29,
    IIIIII\x9dIIIIIII\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00IIIIII)
    artifact_prefix='./'; Test unit written to ./crash-4dc1b47ec0015baf910b70995c37d0b7231129c6
    Base64: SUlJSUlJnUlJSUlJSUkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAAAAAAAAAElJSUlJSSk=
    
    
    opened by eeeeYxN 3
  • Which code to use?

    Which code to use?

    This project references others for compressing sorted arrays, like SIMDCompressionAndIntersection and TurboPFor. AFAICT the core functionality in those projects (i.e. for.c ) seems to be identical, whereas the code in this repo is quite different. What are the differences and why are they there?

    opened by wshager 3
  • Javascript version

    Javascript version

    It would be interesting to see if a javascript version of this library could be created using SIMD.js.

    https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/SIMD https://github.com/tc39/ecmascript_simd https://github.com/jonbrennecke/node-simd

    opened by wshager 31
Releases(v0.1.1)
Owner
Daniel Lemire
Daniel Lemire is a computer science professor. His research is focused on software performance and indexing.
Daniel Lemire
Smaller C is a simple and small single-pass C compiler

Smaller C is a simple and small single-pass C compiler, currently supporting most of the C language common between C89/ANSI C and C99 (minus some C89 and plus some C99 features).

Alexey Frunze 1.1k Jun 27, 2022
An interpreter for a simple imperative language called IPL

For this project, I've implemented an interpreter for IPL, a small imperative language created for educational purposes in the Introduction to Programming course (k04), using some of the techniques I learned from the book Crafting Interpreters.

Jo 10 Mar 9, 2022
Superfast compression library

DENSITY Superfast compression library DENSITY is a free C99, open-source, BSD licensed compression library. It is focused on high-speed compression, a

Centaurean 974 Jun 30, 2022
data compression library for embedded/real-time systems

heatshrink A data compression/decompression library for embedded/real-time systems. Key Features: Low memory usage (as low as 50 bytes) It is useful f

Atomic Object 1.1k Jun 30, 2022
Small strings compression library

SMAZ - compression for very small strings ----------------------------------------- Smaz is a simple compression library suitable for compressing ver

Salvatore Sanfilippo 984 Jun 20, 2022
Compression abstraction library and utilities

Squash - Compresion Abstraction Library

null 362 Jun 6, 2022
Multi-format archive and compression library

Welcome to libarchive! The libarchive project develops a portable, efficient C library that can read and write streaming archives in a variety of form

null 1.8k Jun 25, 2022
is a c++20 compile and runtime Struct Reflections header only library.

is a c++20 compile and runtime Struct Reflections header only library. It allows you to iterate over aggregate type's member variables.

RedSkittleFox 4 Apr 18, 2022
Get the ability to use variable argument lists in C++ without requiring the first parameter! Meant to support a WG14 proposal to fix up not having empty argument lists.

Vargs Alright, it's time to commit code crimes for the greater good! What if you did not need to pass an initial parameter to your C++ ... functions?

Shepherd's Oasis 3 Dec 2, 2021
Simple data packing library (written in C99)

Features Compressed file pack creation Runtime file pack reading Supported operating systems Ubuntu MacOS Windows Build requirements C99 compiler CMak

Nikita Fediuchin 3 Feb 25, 2022
A shellcode crypto-packing tool for PoC (used with msfvenom payloads)

crypter A shellcode crypto-packing tool for PoC (used with msfvenom/binary payloads) This tool is for proof of concept only - please use responsibly.

ripmeep 11 May 30, 2022
a game of packing problems - several thousand of them, to be exact

myriad myriad is a game of packing problems -- several thousand of them, to be exact. install you can compile the game using gcc: gcc -Wall -lncurses

Lux L. 5 Dec 21, 2021
Modo Kit that includes a command for packing UVs, powered by UVPackmaster 2

uvpackit Modo Kit that includes a command for packing UVs, powered by UVPackmaster 2 The included command can be executed with uvp.pack which will ope

null 4 Feb 21, 2022
Draco is a library for compressing and decompressing 3D geometric meshes and point clouds.

Draco is a library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics.

Google 5k Jun 30, 2022
Slow5tools is a toolkit for converting (FAST5 <-> SLOW5), compressing, viewing, indexing and manipulating data in SLOW5 format.

slow5tools Slow5tools is a simple toolkit for converting (FAST5 <-> SLOW5), compressing, viewing, indexing and manipulating data in SLOW5 format. Abou

Hasindu Gamaarachchi 46 Jun 22, 2022
Bolt is an algorithm for compressing vectors of real-valued data and running mathematical operations directly on the compressed representations.

Bolt is an algorithm for compressing vectors of real-valued data and running mathematical operations directly on the compressed representations.

null 2.2k Jun 28, 2022
archiver is a compressing/decompressing tool made for educational purposes

archiver ?? archiver is a compressing/decompressing tool made for educational purposes (specifically, it was a hometask given at a C++ course in the H

Ihor Chovpan 2 Mar 11, 2022
Compressed Log Processor (CLP) is a free tool capable of compressing text logs and searching the compressed logs without decompression.

CLP Compressed Log Processor (CLP) is a tool capable of losslessly compressing text logs and searching the compressed logs without decompression. To l

null 49 Jun 8, 2022
C header library for typed lists (using macros and "template" C).

vector.h C header library for typed lists (using macros and "template" C). Essentially, this is a resizable array of elements of your choosing that is

Christopher Swenson 28 May 6, 2022
Simple and fast C library implementing a thread-safe API to manage hash-tables, linked lists, lock-free ring buffers and queues

libhl C library implementing a set of APIs to efficiently manage some basic data structures such as : hashtables, linked lists, queues, trees, ringbuf

Andrea Guzzo 383 Jul 1, 2022
Random access array of tightly packed unsigned integers

PackedArray: random access array of tightly packed unsigned integers TLDR PackedArray comes to the rescue when you're in a desperate need for an uint9

Gregory Pakosz 131 Jun 22, 2022
A C++20 implementation of safe (wrap around) integers following MISRA C++ rules

PSsimplesafeint A C++20 implementation of safe (wrap around) integers following MISRA C++ rules. I might also work on a C++17 implementation, but this

Peter Sommerlad 33 Jun 4, 2022
C++ class for creating and computing arbitrary-length integers

BigNumber BigNumber is a C++ class that allows for the creation and computation of arbitrary-length integers. The maximum possible length of a BigNumb

Limeoats 127 Jun 9, 2022
Safer integers in C++.

integers is a package for C++ that implements integer types with explicit, well-defined behavior (of your choice!) on overflow, underflow, division by 0, casting, and shifting too far.

Google 167 Jun 24, 2022
Two programs to find the LCM of two positive integers.

LCM-finders LCM-finders? LCM-finders is the repo for my LCM finder projects. I made this program in two similar languages. ?? Note: Two languages mean

Chandula Janith 1 Apr 15, 2022
Given two arrays X and Y of positive integers, find the number of pairs such that x^y > y^x (raised to power of) where x is an element from X and y is an element from Y.

Number-of-pairs Given two arrays X and Y of positive integers, find the number of pairs such that x^y > y^x (raised to power of) where x is an element

Rajesh Kumar Sah 1 Nov 20, 2021
Command-Based Text Editor written in cpp using Linked Lists and Stack

Command Based Text Editor Our goal in this project is to write a command-based text editor in cpp using linked lists and stack. This text editor will

bedirhanbardakci 3 Jun 9, 2021
This is like Inverting Binary Tree, but instead of a Binary Tree it's a File Tree.

Invert File Tree in C++ This is like Inverting Binary Tree, but instead of the Binary Tree it's a File Tree. This is intended as a simple exercise to

Tsoding 11 Jun 18, 2021
A collection of multiple types of lists used during pentesting, collected in one place. List types include usernames, passwords, combos, wordlist and may more..

Access list is a collection of multiple types of lists used during pentesting, collected in one place, created by Undercode This list include a collec

UNDERCODE UTILITIES 7 Jan 6, 2022