ssz icon indicating copy to clipboard operation
ssz copied to clipboard

Explore sha256 conversion optimisation

Open wemeetagain opened this issue 2 years ago • 1 comments

Is your feature request related to a problem? Please describe.

There's a 30% overhead cost converting things back a forth to wasm friendly formats

For context this is only really noticeable when loading states from disk where the full BeaconState has to be hashed. There the saving are in the order of 1s -> 0.7s. However when signing gossip messages the savings are not too significant. Maybe they will be to hash full large ExecutionPayloads.

Summary: cool optimization, not high priority

Describe the solution you'd like


enum HashInputType {
  HashObject,
  Uint8Array,
  Uint64,
}

type HashInput =
  | {type: HashInputType.HashObject; hashObject: HashObject}
  | {type: HashInputType.Uint8Array; uint8Array: Uint8Array}
  | {type: HashInputType.Uint64; uint64: number};

enum HashOutputType {
  HashObject,
  Uint8Array,
}

function setHashInput(input: HashInput, offset32: number): void {
  switch (input.type) {
    case HashInputType.HashObject: {
      const offset4 = offset32 * 8;
      const {hashObject} = input;
      inputUint32Array[offset4 + 0] = hashObject.h0;
      inputUint32Array[offset4 + 1] = hashObject.h1;
      inputUint32Array[offset4 + 2] = hashObject.h2;
      inputUint32Array[offset4 + 3] = hashObject.h3;
      inputUint32Array[offset4 + 4] = hashObject.h4;
      inputUint32Array[offset4 + 5] = hashObject.h5;
      inputUint32Array[offset4 + 6] = hashObject.h6;
      inputUint32Array[offset4 + 7] = hashObject.h7;
      break;
    }

    case HashInputType.Uint8Array: {
      inputUint8Array.set(input.uint8Array, offset32 * 32);
      break;
    }

    case HashInputType.Uint64: {
      const offset4 = offset32 * 8;
      const offset1 = offset32 * 32;
      inputDataView.setUint32(offset1, input.uint64 & 0xffffffff, true);
      inputDataView.setUint32(offset1 + 4, (input.uint64 / NUMBER_2_POW_32) & 0xffffffff, true);
      inputUint32Array[offset4 + 2] = 0;
      inputUint32Array[offset4 + 3] = 0;
      inputUint32Array[offset4 + 4] = 0;
      inputUint32Array[offset4 + 5] = 0;
      inputUint32Array[offset4 + 6] = 0;
      inputUint32Array[offset4 + 7] = 0;
    }
  }
}

export function digest64TypedData(
  input1: HashInput,
  input2: HashInput,
  outputType: HashOutputType.HashObject
): HashObject;

export function digest64TypedData(
  input1: HashInput,
  input2: HashInput,
  outputType: HashOutputType.Uint8Array
): Uint8Array;

/**
 * Digest 2 objects, each has 8 properties from h0 to h7.
 * The performance is a little bit better than digest64 due to the use of Uint32Array
 * and the memory is a little bit better than digest64 due to no temporary Uint8Array.
 * @returns
 */
export function digest64TypedData(
  input1: HashInput,
  input2: HashInput,
  outputType: HashOutputType
): HashObject | Uint8Array {
  setHashInput(input1, 0);
  setHashInput(input2, 1);

  ctx.digest64(wasmInputValue, wasmOutputValue);

  switch (outputType) {
    case HashOutputType.HashObject:
      // extracting numbers from Uint32Array causes more memory
      return byteArrayToHashObject(outputUint8Array);

    case HashOutputType.Uint8Array:
      // TODO: Benchmark fastest way to copy bytes
      return outputUint8Array.slice(0, 32);
  }
}

Additional context

From https://github.com/ChainSafe/ssz/pull/244

wemeetagain avatar Mar 23 '22 03:03 wemeetagain

may be something important to explore to support https://github.com/ChainSafe/lodestar/issues/4005

wemeetagain avatar May 25 '22 16:05 wemeetagain