Skip to content

Conversation

@theweipeng
Copy link
Member

What does this PR do?

Implement the code generator for Map. The code generator will produce JavaScript code based on type descriptions at runtime. The generated code is entirely inlined to minimize the number of function calls and reduce the possibility of IC (Inline Cache) misses.

Input type

Type.map(Type.string(), Type.string())

Output Code

function (fury, external) {
  const br = fury.binaryReader;
  const bw = fury.binaryWriter;
  const cr = fury.classResolver;
  const rr = fury.referenceResolver;

  const readInner = (fromRef) => {

    let count_0 = br.varInt32();
    const result_1 = new Map();
    if (fromRef) {
      rr.reference(result_1)
    }
    while (count_0 > 0) {
      const header = br.uint16();
      const keyHeader = header >> 12;
      const valueHeader = (header >> 8) & 0b00001111;
      const chunkSize = header & 0b11111111;
      br.skip(4);
      const keyIncludeNone = keyHeader & 2;
      const keyTrackingRef = keyHeader & 1;
      const valueIncludeNone = valueHeader & 2;
      const valueTrackingRef = valueHeader & 1;

      for (let index = 0; index < chunkSize; index++) {
        let key;
        let value;
        let flag = 0;
        if (keyTrackingRef || keyIncludeNone) {
          flag = br.uint8();
        }
        switch (flag) {
          case 0:
            key = br.stringOfVarUInt32()
            break;
          case -2:
            key = rr.getReadObject(br.varInt32())
            break;
          case -3:
            key = null;
            break;
          case -1:
            key = br.stringOfVarUInt32()
            break;
        }
        flag = 0;
        if (valueTrackingRef || valueIncludeNone) {
          flag = br.uint8();
        }
        switch (flag) {
          case 0:
            value = br.stringOfVarUInt32()
            break;
          case -2:
            value = rr.getReadObject(br.varInt32())
            break;
          case -3:
            value = null;
            break;
          case -1:
            value = br.stringOfVarUInt32()
            break;
        }
        result_1.set(
          key,
          value
        );
        count_0--;
      }
    }
    return result_1

  };
  const read = () => {

    const refFlag_2 = br.int8();
    switch (refFlag_2) {
      case -1:
      case 0:
        if (br.int16() === 256) {
          cr.readTag(br);
        }
        return readInner(refFlag_2 === 0)
      case -2:
        return rr.getReadObject(br.varUInt32())
      case -3:
        return null
    }

  };
  const writeInner = (v) => {

    bw.varInt32(v.size)
    let lastKeyIsNull_5 = false;
    let lastValueIsNull_6 = false;
    let chunkSize_7 = 0;
    let chunkSizeOffset_8 = 0;

    for (const [k_3, v_4] of v.entries()) {
      let keyIsNull = k_3 === null || k_3 === undefined;
      let valueIsNull = v_4 === null || v_4 === undefined;

      if (lastKeyIsNull_5 !== keyIsNull || lastValueIsNull_6 !== valueIsNull || chunkSize_7 === 0 || chunkSize_7 === 255) {
        if (chunkSize_7 > 0) {
          bw.setUint8Position(chunkSizeOffset_8, chunkSize_7);
          chunkSize_7 = 0;
        }
        chunkSizeOffset_8 = bw.getCursor()
        bw.uint16(((0 & (keyIsNull ? 2 : 0)) << 4) | (0 & (valueIsNull ? 2 : 0)) << 8)
        bw.uint32(3084);

        lastKeyIsNull_5 = keyIsNull;
        lastValueIsNull_6 = valueIsNull;
      }
      if (keyIsNull) {
        bw.uint8(-3)
      }

      bw.stringOfVarUInt32(k_3)

      if (valueIsNull) {
        bw.uint8(-3)
      }

      bw.stringOfVarUInt32(v_4)

      chunkSize_7++;
    }
    if (chunkSize_7 > 0) {
      bw.setUint8Position(chunkSizeOffset_8, chunkSize_7);
    }

  };
  const write = (v) => {

    if (v !== null && v !== undefined) {
      bw.int24(4351);

      writeInner(v);
    } else {
      bw.int8(-3);
    }
  };

  return {
    read,
    readInner,
    write,
    writeInner,
    meta: { "fixedSize": 7, "needToWriteRef": false, "type": 15, "typeId": 16 }
  };
}

Benchmark

benchmark/map.js

We can notice that it isn't much faster than any serializer, because iterating a Map is too expensive. But it is still necessary.

(index) Values
any serialize 215
any deserialize 144
jit serialize 291
jit deserialize 150

@theweipeng theweipeng requested a review from chaokunyang April 24, 2024 17:00
Copy link
Contributor

@LiangliangSui LiangliangSui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Collaborator

@chaokunyang chaokunyang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I didn't find a better way to optimize the generated code

@chaokunyang chaokunyang merged commit 6a0a07b into apache:main Apr 25, 2024
pandalee99 pushed a commit that referenced this pull request Jan 26, 2025
…p serialization (#2025)

## What does this PR do?

This pr provides a new implementation for chunk based map serialization.

## Related issues
#1571
#1549
#1722 
Closes #925 

## Does this PR introduce any user-facing change?

<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fury/issues/new/choose) describing the
need to do so and update the document if necessary.
-->

- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?

## Benchmark

Deserialization are much faster than no-chunk version, serialization are
faster if map size are bigger

Using the benchmark code in
#1722 (comment):

This PR has run faster, it gets up to **3x faster** :
```java
Benchmark                                  (size)  (tracking)  Mode  Cnt     Score       Error  Units
HnBenchmark.testGeneralChunkWriteWithNull      64        true  avgt    3   965.521 ±  1830.936  ns/op
HnBenchmark.testGeneralChunkWriteWithNull      64       false  avgt    3  1060.411 ±  3424.719  ns/op
HnBenchmark.testGeneralChunkWriteWithNull     128        true  avgt    3  2404.445 ±  8687.122  ns/op
HnBenchmark.testGeneralChunkWriteWithNull     128       false  avgt    3  1814.507 ±  1722.751  ns/op
HnBenchmark.testGeneralChunkWriteWithNull     256        true  avgt    3  3944.632 ±  2203.076  ns/op
HnBenchmark.testGeneralChunkWriteWithNull     256       false  avgt    3  3288.805 ±   867.047  ns/op
HnBenchmark.testGeneralWriteWithNull           64        true  avgt    3  1962.688 ±  2828.210  ns/op
HnBenchmark.testGeneralWriteWithNull           64       false  avgt    3  1490.634 ±   962.836  ns/op
HnBenchmark.testGeneralWriteWithNull          128        true  avgt    3  3659.806 ±  7227.436  ns/op
HnBenchmark.testGeneralWriteWithNull          128       false  avgt    3  4084.654 ±  7374.774  ns/op
HnBenchmark.testGeneralWriteWithNull          256        true  avgt    3  9596.658 ± 20767.262  ns/op
HnBenchmark.testGeneralWriteWithNull          256       false  avgt    3  6679.325 ±  5472.179  ns/op
```

With StringMap and IntMap benchmark:
```java

Benchmark                                   (enableChunkEncoding)  (mapSize)   Mode  Cnt        Score          Error  Units
MapSerializationSuite.deserializeIntMap                     false          5  thrpt    3  3804604.842 ± 15328547.705  ops/s
MapSerializationSuite.deserializeIntMap                     false         20  thrpt    3  1254687.969 ±   388949.724  ops/s
MapSerializationSuite.deserializeIntMap                     false         50  thrpt    3   495176.849 ±   335702.097  ops/s
MapSerializationSuite.deserializeIntMap                     false        100  thrpt    3   258875.012 ±    32886.176  ops/s
MapSerializationSuite.deserializeIntMap                     false        200  thrpt    3   134137.015 ±   114908.454  ops/s
MapSerializationSuite.deserializeIntMap                      true          5  thrpt    3  5997383.562 ±  4598913.048  ops/s
MapSerializationSuite.deserializeIntMap                      true         20  thrpt    3  1797855.524 ±  3853406.173  ops/s
MapSerializationSuite.deserializeIntMap                      true         50  thrpt    3   582412.110 ±  1047668.070  ops/s
MapSerializationSuite.deserializeIntMap                      true        100  thrpt    3   389066.866 ±   151297.708  ops/s
MapSerializationSuite.deserializeIntMap                      true        200  thrpt    3   188316.860 ±    35331.909  ops/s
MapSerializationSuite.deserializeStringMap                  false          5  thrpt    3  2898963.533 ±  1930240.310  ops/s
MapSerializationSuite.deserializeStringMap                  false         20  thrpt    3   872196.086 ±   871637.268  ops/s
MapSerializationSuite.deserializeStringMap                  false         50  thrpt    3   308761.737 ±    58099.196  ops/s
MapSerializationSuite.deserializeStringMap                  false        100  thrpt    3   157261.914 ±   397356.241  ops/s
MapSerializationSuite.deserializeStringMap                  false        200  thrpt    3    86576.549 ±   102489.156  ops/s
MapSerializationSuite.deserializeStringMap                   true          5  thrpt    3  3701089.567 ±  1529899.331  ops/s
MapSerializationSuite.deserializeStringMap                   true         20  thrpt    3  1048550.399 ±   130102.760  ops/s
MapSerializationSuite.deserializeStringMap                   true         50  thrpt    3   407559.246 ±    38205.273  ops/s
MapSerializationSuite.deserializeStringMap                   true        100  thrpt    3   172109.437 ±   397927.346  ops/s
MapSerializationSuite.deserializeStringMap                   true        200  thrpt    3    92525.977 ±   379321.772  ops/s
MapSerializationSuite.serializeIntMap                       false          5  thrpt    3  7958692.983 ±  1934287.574  ops/s
MapSerializationSuite.serializeIntMap                       false         20  thrpt    3  2425269.897 ±  3763706.776  ops/s
MapSerializationSuite.serializeIntMap                       false         50  thrpt    3  1079804.122 ±   215967.411  ops/s
MapSerializationSuite.serializeIntMap                       false        100  thrpt    3   369848.671 ±   433172.821  ops/s
MapSerializationSuite.serializeIntMap                       false        200  thrpt    3   192858.945 ±    71543.709  ops/s
MapSerializationSuite.serializeIntMap                        true          5  thrpt    3  7239453.648 ±  3855324.170  ops/s
MapSerializationSuite.serializeIntMap                        true         20  thrpt    3  2137006.685 ±  3823762.656  ops/s
MapSerializationSuite.serializeIntMap                        true         50  thrpt    3   811639.511 ±  2407986.801  ops/s
MapSerializationSuite.serializeIntMap                        true        100  thrpt    3   412728.569 ±   149199.142  ops/s
MapSerializationSuite.serializeIntMap                        true        200  thrpt    3   236602.475 ±   253662.098  ops/s
MapSerializationSuite.serializeStringMap                    false          5  thrpt    3  5821603.026 ±  1397740.496  ops/s
MapSerializationSuite.serializeStringMap                    false         20  thrpt    3  1712819.341 ±   321017.433  ops/s
MapSerializationSuite.serializeStringMap                    false         50  thrpt    3   615260.241 ±   806075.165  ops/s
MapSerializationSuite.serializeStringMap                    false        100  thrpt    3   265117.558 ±   146904.745  ops/s
MapSerializationSuite.serializeStringMap                    false        200  thrpt    3   128618.697 ±    94723.953  ops/s
MapSerializationSuite.serializeStringMap                     true          5  thrpt    3  4503474.325 ± 11254674.336  ops/s
MapSerializationSuite.serializeStringMap                     true         20  thrpt    3  1732501.942 ±   373691.778  ops/s
MapSerializationSuite.serializeStringMap                     true         50  thrpt    3   596678.154 ±   173893.988  ops/s
MapSerializationSuite.serializeStringMap                     true        100  thrpt    3   336814.584 ±   134582.563  ops/s
MapSerializationSuite.serializeStringMap                     true        200  thrpt    3   143124.619 ±   200889.695  ops/s
```

![image](https://github.com/user-attachments/assets/a76301ea-0230-495e-afe9-9d1acea6263d)

![image](https://github.com/user-attachments/assets/2e17d0d0-6851-467d-822b-5a9024252bc4)

![image](https://github.com/user-attachments/assets/b60cdf7f-0bca-4654-a46c-c13d9443661c)

![image](https://github.com/user-attachments/assets/24637c8c-adb4-4de2-a349-df7356b7fdba)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants