LEB128 или Little Endian Base 128 — это сжатие кода переменной длины, используемое для хранения произвольно больших целых чисел в небольшом количестве байтов. LEB128 используется в формате отладочного файла DWARF [1] [2] и двоичном кодировании WebAssembly для всех целочисленных литералов. [3]
Формат LEB128 очень похож на формат переменной длины (VLQ); основное отличие в том, что LEB128 имеет прямой порядок байтов , тогда как переменные длины имеют обратный порядок байтов . Оба формата позволяют хранить небольшие числа в одном байте, а также позволяют кодировать произвольно длинные числа. Существует две версии LEB128: беззнаковый LEB128 и знаковый LEB128. Декодер должен знать, является ли закодированное значение беззнаковым LEB128 или знаковым LEB128.
Чтобы закодировать беззнаковое число с помощью unsigned LEB128 ( ULEB128 ), сначала представьте число в двоичном виде. Затем увеличьте число до кратного 7 бит (так, чтобы если число не равно нулю, то 7 старших бит не были бы все 0). Разбейте число на группы по 7 бит. Выведите один закодированный байт для каждой 7-битной группы, от наименее значимой к наиболее значимой группе. Каждый байт будет иметь группу в своих 7 наименее значимых битах. Установите наиболее значимый бит в каждом байте, кроме последнего байта. Число ноль обычно кодируется как один байт 0x00. WebAssembly допускает альтернативные кодировки нуля (0x80 0x00, 0x80 0x80 0x00, ...). [3]
В качестве примера, вот как кодируется беззнаковое число 624485:
MSB ------------------ LSB 10011000011101100101 В необработанном двоичном формате 010011000011101100101 Дополнено до кратного 7 бит 0100110 0001110 1100101 Разделить на 7-битные группы00100110 10001110 11100101 Добавить старшие 1 бита ко всем группам, кроме последней (самой значимой), для формирования байтов 0x26 0x8E 0xE5 В шестнадцатеричном формате→ 0xE5 0x8E 0x26 Выходной поток (от младшего байта к старшему)
Беззнаковый LEB128 и VLQ ( переменная длина ) сжимают любое заданное целое число не только в одинаковое количество бит, но и в абсолютно одинаковые биты — эти два формата отличаются только тем, как именно организованы эти биты.
Знаковое число представляется аналогичным образом: начиная с -битного представления в дополнительном коде , где — кратно 7, число разбивается на группы, как и для беззнакового кодирования.
Например, знаковое число -123456 кодируется как 0xC0 0xBB 0x78:
MSB ------------------ LSB 11110001001000000 Двоичное кодирование 123456 000011110001001000000 Как 21-битное число 111100001110110111111 Отрицание всех битов ( дополнение до единицы ) 111100001110111000000 Добавление единицы (дополнение до двух) 1111000 0111011 1000000 Разделить на 7-битные группы01111000 10111011 11000000 Добавить старшие 1 бита ко всем группам, кроме последней (самой значимой), для формирования байтов 0x78 0xBB 0xC0 В шестнадцатеричном формате→ 0xC0 0xBB 0x78 Выходной поток (от младшего байта к старшему)
Простая скалярная реализация декодирования LEB128 довольно медленная, тем более на современном оборудовании, где неверное предсказание ветвления относительно дорого. В ряде статей представлены методы SIMD для ускорения декодирования (в этих статьях он называется VByte, но это другое название для того же кодирования). В статье «Vectorized VByte Decoding» [4] представлен «Masked VByte», который продемонстрировал скорость 650–2700 миллионов целых чисел в секунду на обычном оборудовании Haswell в зависимости от плотности кодирования. В последующей статье представлен вариант кодирования «Stream VByte: Faster Byte Oriented Integer Compression» [5] , который увеличил скорость до более чем 4 миллиардов целых чисел в секунду. Это потоковое кодирование отделяет поток управления от закодированных данных, поэтому оно несовместимо с LEB128 на двоичном уровне.
do { byte = младшие 7 бит значения ; value >>= 7 ; if ( value != 0 ) / * осталось еще байты * / установить старший бит байта ; emit byte ; } while ( value ! = 0 ) ;
больше = 1 ; отрицательно = ( значение < 0 ); /* размер в битах значения переменной, например, 64, если тип значения int64_t * / размер = количество бит в знаковом целом числе ; while ( more ) { byte = младшие 7 бит значения ; value >>= 7 ; / * следующее необходимо только в том случае, если реализация >>= использует логический сдвиг , а не арифметический сдвиг для знакового левого операнда */ if ( negative ) value |= ( ~ 0 << ( size - 7 )); /* расширение знака */ / * знаковый бит байта - второй старший бит (0x40) */ if (( value == 0 && знаковый бит байта очищен ) || ( value == -1 && знаковый бит байта установлен ) ) more = 0 ; else установить старший бит байта ; выдать байт ; }
результат = 0 ; сдвиг = 0 ; while ( истина ) { байт = следующий байт во входных данных ; результат | = ( младшие 7 бит байта ) << сдвиг ; если ( старший бит байта == 0 ) break ; сдвиг + = 7 ; }
результат = 0 ; сдвиг = 0 ; /* размер в битах переменной результата, например, 64, если тип результата — int64_t */ размер = количество бит в знаковом целом числе ; do { byte = следующий байт на входе ; result | = ( младшие 7 бит байта << shift ) ; shift + = 7 ; } while ( старший бит байта ! = 0 ) ; / * знаковый бит байта - второй старший бит (0x40) */ if (( shift < size ) && ( установлен знаковый бит байта ) ) /* расширение знака */ result |= ( ~ 0 << shift );
функция encodeUnSignedInt32toLeb128 ( значение ) { var rawbine = value . toString ( 2 ); // В необработанном двоичном формате var paded = rawbine . padStart ( Math . ceil ((( rawbine . length ) / 7 )) * 7 , '0' ) // Дополняется до кратного 7 бит var splited = paded . match ( /. {1,7}/g ); // Разбивается на 7-битные группы const result = []; // Добавляет старшие 1-е биты ко всем группам, кроме последней (самой значимой), для формирования байтов var y = splited . length - 1 for ( let i = 0 ; i < splited . length ; i ++ ) { if ( i === 0 ) { var str = "0" + splited [ i ]; result [ y ] = parseInt ( str , 2 ). toString ( 16 ). padStart ( 2 , '0' ); } else { var str = "1" + разделенный [ i ]; результат [ y ] = parseInt ( str , 2 ). toString ( 16 ). PadStart ( 2 , '0' ); } y -- ; } вернуть результат . join ( '' ); };
function decodeLeb128toUnSignedInt32 ( value ) { var tabvalue = value.split ( "" ); var result = " " ; const tabtemp = []; var y = tabvalue.length / 2-1 ; for ( let i = 0 ; i < tabvalue.length ; i ++ ) { if ( i % 2 != 0 ) { tabtemp [ y ] = (( parseInt ( tabvalue [ i - 1 ] , 16 ) .toString ( 2 )). padStart ( 4 , ' 0 ' ) + ( parseInt ( tabvalue [ i ], 16 ) .toString ( 2 )). padStart ( 4 , ' 0 ' ) ). slice ( 1 ) ; y -- ; } } result = parseInt ( tabtemp.join ( ' ' ) , 2 ) . toString ( 10 ) вернуть результат ; };
const encodeSignedLeb128FromInt32 = ( value ) => { value |= 0 ; const result = []; while ( true ) { const byte_ = value & 0x7f ; value >>= 7 ; if ( ( value === 0 && ( byte_ & 0x40 ) === 0 ) || ( value === - 1 && ( byte_ & 0x40 ) ! == 0 ) ) { result.push ( byte_ ) ; return result ; } result.push ( byte_ | 0x80 ) ; } } ;
const decodeSignedLeb128 = ( input ) => { let result = 0 ; let shift = 0 ; while ( true ) { const byte = input . shift (); result |= ( byte & 0x7f ) << shift ; shift += 7 ; if (( 0x80 & byte ) === 0 ) { if ( shift < 32 && ( byte & 0x40 ) !== 0 ) { return result | ( ~ 0 << shift ); } return result ; } } };
{{cite journal}}
: Цитировать журнал требует |journal=
( помощь )