Skip to content
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

perf(cbor): for encoding numbers, bigints, and dates #6214

Merged
merged 3 commits into from
Nov 28, 2024

Conversation

BlackAsLight
Copy link
Contributor

@BlackAsLight BlackAsLight commented Nov 27, 2024

This pull request offers performance improvements to the encoding of numbers, BigInts and dates into the cbor format. The current version of cbor appears to beat msgpack in small numbers but perform worse with larger numbers. These changes cause cbor to have the same performance for small numbers and better performance for larger numbers. Beating msgpack in all ranges.

I did try similar changes with Uint8Array and Arrays, but Arrays did worse overall. For Uint8Arrays, I was able to get a performance boost for small lengths of data, but very easily worse for large lengths of data. There was also a breaking change with encoding Uint8Arrays for one to get that performance boost.

Benchmark Source Code
  • std/cbor/deno.json
{
  "name": "@std/cbo",
  "version": "0.1.2",
  "exports": {
    ".": "./mod.ts",
    "./array-encoder-stream": "./array_encoder_stream.ts",
    "./byte-encoder-stream": "./byte_encoder_stream.ts",
    "./decode-cbor-sequence": "./decode_cbor_sequence.ts",
    "./decode-cbor": "./decode_cbor.ts",
    "./encode-cbor-sequence": "./encode_cbor_sequence.ts",
    "./encode-cbor": "./encode_cbor.ts",
    "./map-encoder-stream": "./map_encoder_stream.ts",
    "./sequence-decoder-stream": "./sequence_decoder_stream.ts",
    "./sequence-encoder-stream": "./sequence_encoder_stream.ts",
    "./tag": "./tag.ts",
    "./text-encoder-stream": "./text_encoder_stream.ts",
    "./types": "./types.ts"
  }
}
  • std/bench
import { encodeCbor as encodeCborOld } from "@std/cbor";
import { encodeCbor as encodeCborNew } from "@std/cbo";
import { encode as encodeMsg } from "@std/msgpack";

function random(start: number, end: number): number {
  return Math.floor(Math.random() * (end - start) + start);
}

const Float = Math.random() * 2 ** 8;
const Byte0 = random(0, 24);
const Byte1 = random(24, 2 ** 8);
const Byte2 = random(2 ** 8, 2 ** 16);
const Byte4 = random(2 ** 16, 2 ** 32);
const Byte8 = random(2 ** 32, 2 ** 64);
const Now = new Date();

Deno.bench({ name: "Cbor Old: Float", group: "Float" }, () => {
  encodeCborOld(Float);
});

Deno.bench({ name: "Cbor New: Float", group: "Float" }, () => {
  encodeCborNew(Float);
});

Deno.bench({ name: "MsgPack: Float", group: "Float" }, () => {
  encodeMsg(Float);
});

Deno.bench(
  { name: "Cbor Old: Number - 0 Byte Length", group: "N:Byte0" },
  () => {
    encodeCborOld(Byte0);
  },
);

Deno.bench(
  { name: "Cbor New: Number - 0 Byte Length", group: "N:Byte0" },
  () => {
    encodeCborNew(Byte0);
  },
);

Deno.bench(
  { name: "MsgPack: Number - 0 Byte Length", group: "N:Byte0" },
  () => {
    encodeMsg(Byte0);
  },
);

Deno.bench(
  { name: "Cbor Old: Number - 1 Byte Length", group: "N:Byte1" },
  () => {
    encodeCborOld(Byte1);
  },
);

Deno.bench(
  { name: "Cbor New: Number - 1 Byte Length", group: "N:Byte1" },
  () => {
    encodeCborNew(Byte1);
  },
);

Deno.bench(
  { name: "MsgPack: Number - 1 Byte Length", group: "N:Byte1" },
  () => {
    encodeMsg(Byte1);
  },
);

Deno.bench(
  { name: "Cbor Old: Number - 2 Byte Length", group: "N:Byte2" },
  () => {
    encodeCborOld(Byte2);
  },
);

Deno.bench(
  { name: "Cbor New: Number - 2 Byte Length", group: "N:Byte2" },
  () => {
    encodeCborNew(Byte2);
  },
);

Deno.bench(
  { name: "MsgPack: Number - 2 Byte Length", group: "N:Byte2" },
  () => {
    encodeMsg(Byte2);
  },
);

Deno.bench(
  { name: "Cbor Old: Number - 4 Byte Length", group: "N:Byte4" },
  () => {
    encodeCborOld(Byte4);
  },
);

Deno.bench(
  { name: "Cbor New: Number - 4 Byte Length", group: "N:Byte4" },
  () => {
    encodeCborNew(Byte4);
  },
);

Deno.bench(
  { name: "MsgPack: Number - 4 Byte Length", group: "N:Byte4" },
  () => {
    encodeMsg(Byte4);
  },
);

Deno.bench(
  { name: "Cbor Old: Number - 8 Byte Length", group: "N:Byte8" },
  () => {
    encodeCborOld(Byte8);
  },
);

Deno.bench(
  { name: "Cbor New: Number - 8 Byte Length", group: "N:Byte8" },
  () => {
    encodeCborNew(Byte8);
  },
);

Deno.bench(
  { name: "MsgPack: Number - 8 Byte Length", group: "N:Byte8" },
  () => {
    encodeMsg(Byte8);
  },
);

Deno.bench(
  { name: "Cbor Old: BigInt - 0 Byte Length", group: "B:Byte0" },
  () => {
    encodeCborOld(BigInt(Byte0));
  },
);

Deno.bench(
  { name: "Cbor New: BigInt - 0 Byte Length", group: "B:Byte0" },
  () => {
    encodeCborNew(BigInt(Byte0));
  },
);

Deno.bench(
  { name: "MsgPack: BigInt - 0 Byte Length", group: "B:Byte0" },
  () => {
    encodeMsg(BigInt(Byte0));
  },
);

Deno.bench(
  { name: "Cbor Old: BigInt - 1 Byte Length", group: "B:Byte1" },
  () => {
    encodeCborOld(BigInt(Byte1));
  },
);

Deno.bench(
  { name: "Cbor New: BigInt - 1 Byte Length", group: "B:Byte1" },
  () => {
    encodeCborNew(BigInt(Byte1));
  },
);

Deno.bench(
  { name: "MsgPack: BigInt - 1 Byte Length", group: "B:Byte1" },
  () => {
    encodeMsg(BigInt(Byte1));
  },
);

Deno.bench(
  { name: "Cbor Old: BigInt - 2 Byte Length", group: "B:Byte2" },
  () => {
    encodeCborOld(BigInt(Byte2));
  },
);

Deno.bench(
  { name: "Cbor New: BigInt - 2 Byte Length", group: "B:Byte2" },
  () => {
    encodeCborNew(BigInt(Byte2));
  },
);

Deno.bench(
  { name: "MsgPack: BigInt - 2 Byte Length", group: "B:Byte2" },
  () => {
    encodeMsg(BigInt(Byte2));
  },
);

Deno.bench(
  { name: "Cbor Old: BigInt - 4 Byte Length", group: "B:Byte4" },
  () => {
    encodeCborOld(BigInt(Byte4));
  },
);

Deno.bench(
  { name: "Cbor New: BigInt - 4 Byte Length", group: "B:Byte4" },
  () => {
    encodeCborNew(BigInt(Byte4));
  },
);

Deno.bench(
  { name: "MsgPack: BigInt - 4 Byte Length", group: "B:Byte4" },
  () => {
    encodeMsg(BigInt(Byte4));
  },
);

Deno.bench(
  { name: "Cbor Old: BigInt - 8 Byte Length", group: "B:Byte8" },
  () => {
    encodeCborOld(BigInt(Byte8));
  },
);

Deno.bench(
  { name: "Cbor New: BigInt - 8 Byte Length", group: "B:Byte8" },
  () => {
    encodeCborNew(BigInt(Byte8));
  },
);

Deno.bench(
  { name: "MsgPack: BigInt - 8 Byte Length", group: "B:Byte8" },
  () => {
    encodeMsg(BigInt(Byte8));
  },
);

Deno.bench({ name: "Cbor Old: Date", group: "Date" }, () => {
  encodeCborOld(Now);
});

Deno.bench({ name: "Cbor New: Date", group: "Date" }, () => {
  encodeCborNew(Now);
});
Benchmark Results
Check file:///Users/soul/Projects/std/bench
    CPU | Apple M1
Runtime | Deno 2.1.1 (aarch64-apple-darwin)

file:///Users/soul/Projects/std/bench

benchmark                          time/iter (avg)        iter/s      (min … max)           p75      p99     p995
---------------------------------- ----------------------------- --------------------- --------------------------

group Float
Cbor Old: Float                           802.6 ns     1,246,000 (707.0 ns …   5.1 µs) 729.2 ns   5.1 µs   5.1 µs
Cbor New: Float                           182.9 ns     5,467,000 (166.6 ns … 206.2 ns) 189.1 ns 203.6 ns 204.5 ns
MsgPack: Float                            328.9 ns     3,040,000 (299.1 ns … 483.4 ns) 333.5 ns 390.9 ns 483.4 ns

summary
  Cbor New: Float
     1.80x faster than MsgPack: Float
     4.39x faster than Cbor Old: Float

group N:Byte0
Cbor Old: Number - 0 Byte Length          158.4 ns     6,311,000 (141.3 ns … 175.9 ns) 164.9 ns 174.2 ns 175.5 ns
Cbor New: Number - 0 Byte Length          161.4 ns     6,195,000 (139.5 ns … 184.9 ns) 169.5 ns 181.7 ns 183.5 ns
MsgPack: Number - 0 Byte Length           321.9 ns     3,107,000 (299.7 ns … 349.1 ns) 329.0 ns 347.3 ns 349.1 ns

summary
  Cbor Old: Number - 0 Byte Length
     1.02x faster than Cbor New: Number - 0 Byte Length
     2.03x faster than MsgPack: Number - 0 Byte Length

group N:Byte1
Cbor Old: Number - 1 Byte Length          160.5 ns     6,230,000 (143.8 ns … 195.0 ns) 165.6 ns 178.3 ns 185.4 ns
Cbor New: Number - 1 Byte Length          160.8 ns     6,217,000 (140.1 ns … 186.2 ns) 169.0 ns 180.4 ns 181.4 ns
MsgPack: Number - 1 Byte Length           321.9 ns     3,106,000 (302.5 ns … 350.8 ns) 327.8 ns 347.2 ns 350.8 ns

summary
  Cbor Old: Number - 1 Byte Length
     1.00x faster than Cbor New: Number - 1 Byte Length
     2.01x faster than MsgPack: Number - 1 Byte Length

group N:Byte2
Cbor Old: Number - 2 Byte Length          726.2 ns     1,377,000 (698.9 ns … 766.2 ns) 737.4 ns 766.2 ns 766.2 ns
Cbor New: Number - 2 Byte Length          205.9 ns     4,857,000 (193.1 ns … 220.7 ns) 209.2 ns 219.3 ns 220.4 ns
MsgPack: Number - 2 Byte Length           322.4 ns     3,102,000 (298.1 ns … 350.9 ns) 330.4 ns 348.0 ns 350.9 ns

summary
  Cbor New: Number - 2 Byte Length
     1.57x faster than MsgPack: Number - 2 Byte Length
     3.53x faster than Cbor Old: Number - 2 Byte Length

group N:Byte4
Cbor Old: Number - 4 Byte Length          801.8 ns     1,247,000 (761.9 ns … 957.0 ns) 810.7 ns 957.0 ns 957.0 ns
Cbor New: Number - 4 Byte Length          215.6 ns     4,638,000 (191.6 ns … 476.4 ns) 213.6 ns 383.6 ns 433.1 ns
MsgPack: Number - 4 Byte Length           329.5 ns     3,035,000 (299.3 ns … 576.5 ns) 331.1 ns 502.3 ns 576.5 ns

summary
  Cbor New: Number - 4 Byte Length
     1.53x faster than MsgPack: Number - 4 Byte Length
     3.72x faster than Cbor Old: Number - 4 Byte Length

group N:Byte8
Cbor Old: Number - 8 Byte Length          778.0 ns     1,285,000 (756.6 ns … 837.3 ns) 786.5 ns 837.3 ns 837.3 ns
Cbor New: Number - 8 Byte Length          194.4 ns     5,143,000 (174.3 ns … 216.5 ns) 200.6 ns 213.0 ns 214.8 ns
MsgPack: Number - 8 Byte Length           328.3 ns     3,046,000 (303.6 ns … 416.6 ns) 335.2 ns 378.2 ns 416.6 ns

summary
  Cbor New: Number - 8 Byte Length
     1.69x faster than MsgPack: Number - 8 Byte Length
     4.00x faster than Cbor Old: Number - 8 Byte Length

group B:Byte0
Cbor Old: BigInt - 0 Byte Length          194.1 ns     5,151,000 (177.7 ns … 292.6 ns) 194.9 ns 239.8 ns 253.8 ns
Cbor New: BigInt - 0 Byte Length          190.2 ns     5,257,000 (173.1 ns … 227.9 ns) 195.2 ns 219.5 ns 225.5 ns
MsgPack: BigInt - 0 Byte Length           335.9 ns     2,977,000 (306.5 ns … 409.1 ns) 346.1 ns 394.5 ns 409.1 ns

summary
  Cbor New: BigInt - 0 Byte Length
     1.02x faster than Cbor Old: BigInt - 0 Byte Length
     1.77x faster than MsgPack: BigInt - 0 Byte Length

group B:Byte1
Cbor Old: BigInt - 1 Byte Length          195.6 ns     5,111,000 (182.9 ns … 230.7 ns) 197.6 ns 217.5 ns 225.2 ns
Cbor New: BigInt - 1 Byte Length          192.7 ns     5,189,000 (176.6 ns … 218.2 ns) 197.6 ns 209.3 ns 214.3 ns
MsgPack: BigInt - 1 Byte Length           327.9 ns     3,050,000 (306.1 ns … 401.4 ns) 334.4 ns 388.5 ns 401.4 ns

summary
  Cbor New: BigInt - 1 Byte Length
     1.01x faster than Cbor Old: BigInt - 1 Byte Length
     1.70x faster than MsgPack: BigInt - 1 Byte Length

group B:Byte2
Cbor Old: BigInt - 2 Byte Length          790.5 ns     1,265,000 (767.7 ns … 845.7 ns) 801.1 ns 845.7 ns 845.7 ns
Cbor New: BigInt - 2 Byte Length          246.3 ns     4,060,000 (238.0 ns … 288.5 ns) 246.7 ns 277.0 ns 284.1 ns
MsgPack: BigInt - 2 Byte Length           322.6 ns     3,100,000 (304.9 ns … 351.4 ns) 329.8 ns 337.6 ns 351.4 ns

summary
  Cbor New: BigInt - 2 Byte Length
     1.31x faster than MsgPack: BigInt - 2 Byte Length
     3.21x faster than Cbor Old: BigInt - 2 Byte Length

group B:Byte4
Cbor Old: BigInt - 4 Byte Length          854.1 ns     1,171,000 (828.4 ns … 902.3 ns) 863.3 ns 902.3 ns 902.3 ns
Cbor New: BigInt - 4 Byte Length          281.4 ns     3,554,000 (270.3 ns … 319.1 ns) 287.4 ns 317.5 ns 319.1 ns
MsgPack: BigInt - 4 Byte Length           320.2 ns     3,123,000 (303.9 ns … 354.0 ns) 325.9 ns 353.6 ns 354.0 ns

summary
  Cbor New: BigInt - 4 Byte Length
     1.14x faster than MsgPack: BigInt - 4 Byte Length
     3.04x faster than Cbor Old: BigInt - 4 Byte Length

group B:Byte8
Cbor Old: BigInt - 8 Byte Length          786.9 ns     1,271,000 (752.7 ns … 872.1 ns) 799.3 ns 872.1 ns 872.1 ns
Cbor New: BigInt - 8 Byte Length          247.0 ns     4,048,000 (225.4 ns … 346.1 ns) 249.0 ns 291.8 ns 300.6 ns
MsgPack: BigInt - 8 Byte Length           347.0 ns     2,882,000 (322.5 ns … 413.4 ns) 351.7 ns 401.4 ns 413.4 ns

summary
  Cbor New: BigInt - 8 Byte Length
     1.41x faster than MsgPack: BigInt - 8 Byte Length
     3.19x faster than Cbor Old: BigInt - 8 Byte Length

group Date
Cbor Old: Date                              1.1 µs       883,200 (  1.1 µs …   1.2 µs)   1.1 µs   1.2 µs   1.2 µs
Cbor New: Date                            406.1 ns     2,463,000 (385.2 ns … 465.4 ns) 412.7 ns 460.0 ns 465.4 ns

summary
  Cbor New: Date
     2.79x faster than Cbor Old: Date

@BlackAsLight BlackAsLight requested a review from kt3k as a code owner November 27, 2024 02:37
@github-actions github-actions bot added the cbor label Nov 27, 2024
- Revert smaller number encoding to use previous method as the new method is unnecessary for such a small size.
Copy link

codecov bot commented Nov 28, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.53%. Comparing base (9daae05) to head (f5707e0).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6214      +/-   ##
==========================================
- Coverage   96.53%   96.53%   -0.01%     
==========================================
  Files         534      534              
  Lines       40922    40942      +20     
  Branches     6131     6131              
==========================================
+ Hits        39505    39522      +17     
- Misses       1375     1378       +3     
  Partials       42       42              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@kt3k kt3k left a comment

Choose a reason for hiding this comment

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

Thanks for the careful benchmark works! Looks like huge improvement on performance.

LGTM

@kt3k kt3k merged commit 09e426b into denoland:main Nov 28, 2024
18 checks passed
@BlackAsLight BlackAsLight deleted the cbor_performance branch November 28, 2024 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants