-
-
Notifications
You must be signed in to change notification settings - Fork 416
feat: use different snappy implementations for different topics #8670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
twoeths
wants to merge
9
commits into
cayman/snappy
Choose a base branch
from
te/cayman/snappy
base: cayman/snappy
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4419e35
feat: add snappyjs implementation in ts
twoeths cbe7cfa
feat: implement ISnappyDecompressor interface
twoeths e717d37
feat: use different uncompressor based on topic type
twoeths 3f091e8
fix: remove snappy-js
twoeths c94ede7
fix: make consistent behaviour across implementations
twoeths 2d4de6a
fix: use snappy-wasm for beacon_block and data_column_sidecar only
twoeths a74d316
fix: use Buffer.alloc()
twoeths 95416e8
fix: update snappy benchmark using ISnappyDecompressor interface
twoeths 732860c
chore: link to snappy-js upstream repo
twoeths File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import {GossipType} from "../interface.js"; | ||
| import {ISnappyDecompressor} from "./interface.js"; | ||
| import {SnappyDecompressor} from "./snappy-js/decompressor.js"; | ||
| import {SnappyWasmDecompressor} from "./snappy-wasm.js"; | ||
|
|
||
| /** | ||
| * for decompression, we use different implementations based on topic type | ||
| * snappy-wasm is generally better for larger payloads and snappyjs is better for smaller payloads | ||
| */ | ||
| export function getSnappyDecompressor(topicType: GossipType, data: Uint8Array): ISnappyDecompressor { | ||
| switch (topicType) { | ||
| case GossipType.beacon_attestation: | ||
|
||
| return new SnappyDecompressor(data); | ||
| default: | ||
| return new SnappyWasmDecompressor(data); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export interface ISnappyDecompressor { | ||
| readUncompressedLength(): number; | ||
| uncompressInto(outBuffer: Uint8Array): boolean; | ||
| } |
212 changes: 212 additions & 0 deletions
212
packages/beacon-node/src/network/gossip/snappy/snappy-js/compressor.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| const BLOCK_LOG = 16; | ||
| const BLOCK_SIZE = 1 << BLOCK_LOG; | ||
|
|
||
| const MAX_HASH_TABLE_BITS = 14; | ||
| const globalHashTables = new Array(MAX_HASH_TABLE_BITS + 1); | ||
|
|
||
| export class SnappyCompressor { | ||
| constructor(private readonly array: Uint8Array) {} | ||
|
|
||
| maxCompressedLength(): number { | ||
| const sourceLen = this.array.length; | ||
| return 32 + sourceLen + Math.floor(sourceLen / 6); | ||
| } | ||
|
|
||
| compressToBuffer(outBuffer: Uint8Array): number { | ||
| const array = this.array; | ||
| const length = array.length; | ||
| let pos = 0; | ||
| let outPos = 0; | ||
|
|
||
| let fragmentSize: number; | ||
|
|
||
| outPos = putVarint(length, outBuffer, outPos); | ||
| while (pos < length) { | ||
| fragmentSize = Math.min(length - pos, BLOCK_SIZE); | ||
| outPos = compressFragment(array, pos, fragmentSize, outBuffer, outPos); | ||
| pos += fragmentSize; | ||
| } | ||
|
|
||
| return outPos; | ||
| } | ||
| } | ||
|
|
||
| function hashFunc(key: number, hashFuncShift: number): number { | ||
| return (key * 0x1e35a7bd) >>> hashFuncShift; | ||
| } | ||
|
|
||
| function load32(array: Uint8Array, pos: number): number { | ||
| return array[pos] + (array[pos + 1] << 8) + (array[pos + 2] << 16) + (array[pos + 3] << 24); | ||
| } | ||
|
|
||
| function equals32(array: Uint8Array, pos1: number, pos2: number): boolean { | ||
| return ( | ||
| array[pos1] === array[pos2] && | ||
| array[pos1 + 1] === array[pos2 + 1] && | ||
| array[pos1 + 2] === array[pos2 + 2] && | ||
| array[pos1 + 3] === array[pos2 + 3] | ||
| ); | ||
| } | ||
|
|
||
| function copyBytes(fromArray: Uint8Array, fromPos: number, toArray: Uint8Array, toPos: number, length: number): void { | ||
| for (let i = 0; i < length; i++) { | ||
| toArray[toPos + i] = fromArray[fromPos + i]; | ||
| } | ||
| } | ||
|
|
||
| function emitLiteral(input: Uint8Array, ip: number, len: number, output: Uint8Array, op: number): number { | ||
| if (len <= 60) { | ||
| output[op] = (len - 1) << 2; | ||
| op += 1; | ||
| } else if (len < 256) { | ||
| output[op] = 60 << 2; | ||
| output[op + 1] = len - 1; | ||
| op += 2; | ||
| } else { | ||
| output[op] = 61 << 2; | ||
| output[op + 1] = (len - 1) & 0xff; | ||
| output[op + 2] = (len - 1) >>> 8; | ||
| op += 3; | ||
| } | ||
| copyBytes(input, ip, output, op, len); | ||
| return op + len; | ||
| } | ||
|
|
||
| function emitCopyLessThan64(output: Uint8Array, op: number, offset: number, len: number): number { | ||
| if (len < 12 && offset < 2048) { | ||
| output[op] = 1 + ((len - 4) << 2) + ((offset >>> 8) << 5); | ||
| output[op + 1] = offset & 0xff; | ||
| return op + 2; | ||
| } | ||
| output[op] = 2 + ((len - 1) << 2); | ||
| output[op + 1] = offset & 0xff; | ||
| output[op + 2] = offset >>> 8; | ||
| return op + 3; | ||
| } | ||
|
|
||
| function emitCopy(output: Uint8Array, op: number, offset: number, len: number): number { | ||
| while (len >= 68) { | ||
| op = emitCopyLessThan64(output, op, offset, 64); | ||
| len -= 64; | ||
| } | ||
| if (len > 64) { | ||
| op = emitCopyLessThan64(output, op, offset, 60); | ||
| len -= 60; | ||
| } | ||
| return emitCopyLessThan64(output, op, offset, len); | ||
| } | ||
|
|
||
| function compressFragment(input: Uint8Array, ip: number, inputSize: number, output: Uint8Array, op: number): number { | ||
| let hashTableBits = 1; | ||
| while (1 << hashTableBits <= inputSize && hashTableBits <= MAX_HASH_TABLE_BITS) { | ||
| hashTableBits += 1; | ||
| } | ||
| hashTableBits -= 1; | ||
| const hashFuncShift = 32 - hashTableBits; | ||
|
|
||
| if (typeof globalHashTables[hashTableBits] === "undefined") { | ||
| globalHashTables[hashTableBits] = new Uint16Array(1 << hashTableBits); | ||
| } | ||
| const hashTable = globalHashTables[hashTableBits]; | ||
| for (let i = 0; i < hashTable.length; i++) { | ||
| hashTable[i] = 0; | ||
| } | ||
|
|
||
| const ipEnd = ip + inputSize; | ||
| let ipLimit: number; | ||
| const baseIp = ip; | ||
| let nextEmit = ip; | ||
|
|
||
| let hash: number; | ||
| let nextHash: number; | ||
| let nextIp: number; | ||
| let candidate = 0; | ||
| let skip: number; | ||
| let bytesBetweenHashLookups: number; | ||
| let base: number; | ||
| let matched: number; | ||
| let offset: number; | ||
| let prevHash: number; | ||
| let curHash: number; | ||
| let flag = true; | ||
|
|
||
| const INPUT_MARGIN = 15; | ||
| if (inputSize >= INPUT_MARGIN) { | ||
| ipLimit = ipEnd - INPUT_MARGIN; | ||
|
|
||
| ip += 1; | ||
| nextHash = hashFunc(load32(input, ip), hashFuncShift); | ||
|
|
||
| while (flag) { | ||
| skip = 32; | ||
| nextIp = ip; | ||
| do { | ||
| ip = nextIp; | ||
| hash = nextHash; | ||
| bytesBetweenHashLookups = skip >>> 5; | ||
| skip += 1; | ||
| nextIp = ip + bytesBetweenHashLookups; | ||
| if (ip > ipLimit) { | ||
| flag = false; | ||
| break; | ||
| } | ||
| nextHash = hashFunc(load32(input, nextIp), hashFuncShift); | ||
| candidate = baseIp + hashTable[hash]; | ||
| hashTable[hash] = ip - baseIp; | ||
| } while (!equals32(input, ip, candidate)); | ||
|
|
||
| if (!flag) { | ||
| break; | ||
| } | ||
|
|
||
| op = emitLiteral(input, nextEmit, ip - nextEmit, output, op); | ||
|
|
||
| do { | ||
| base = ip; | ||
| matched = 4; | ||
| while (ip + matched < ipEnd && input[ip + matched] === input[candidate + matched]) { | ||
| matched += 1; | ||
| } | ||
| ip += matched; | ||
| offset = base - candidate; | ||
| op = emitCopy(output, op, offset, matched); | ||
|
|
||
| nextEmit = ip; | ||
| if (ip >= ipLimit) { | ||
| flag = false; | ||
| break; | ||
| } | ||
| prevHash = hashFunc(load32(input, ip - 1), hashFuncShift); | ||
| hashTable[prevHash] = ip - 1 - baseIp; | ||
| curHash = hashFunc(load32(input, ip), hashFuncShift); | ||
| candidate = baseIp + hashTable[curHash]; | ||
| hashTable[curHash] = ip - baseIp; | ||
| } while (equals32(input, ip, candidate)); | ||
|
|
||
| if (!flag) { | ||
| break; | ||
| } | ||
|
|
||
| ip += 1; | ||
| nextHash = hashFunc(load32(input, ip), hashFuncShift); | ||
| } | ||
| } | ||
|
|
||
| if (nextEmit < ipEnd) { | ||
| op = emitLiteral(input, nextEmit, ipEnd - nextEmit, output, op); | ||
| } | ||
|
|
||
| return op; | ||
| } | ||
|
|
||
| function putVarint(value: number, output: Uint8Array, op: number): number { | ||
| do { | ||
| output[op] = value & 0x7f; | ||
| value = value >>> 7; | ||
| if (value > 0) { | ||
| output[op] += 0x80; | ||
| } | ||
| op += 1; | ||
| } while (value > 0); | ||
| return op; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.