Skip to content

Commit

Permalink
Fix support on all Javscript runtimes
Browse files Browse the repository at this point in the history
  • Loading branch information
devraymondsh committed Jan 10, 2024
1 parent 04b458b commit f5af0e4
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 250 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ jobs:
- uses: actions/checkout@v3
- uses: goto-bus-stop/setup-zig@v2
- name: Install npm
run: sudo apt-get install -y curl unzip && curl -fsSL https://fnm.vercel.app/install | bash && export PATH="/$HOME/.local/share/fnm:$PATH" && eval "`fnm env`" && fnm install v20.10.0 && fnm use v20.10.0 && npm -g i npm@latest
run: sudo apt-get install -y curl unzip && curl -fsSL https://fnm.vercel.app/install | bash && export PATH="/$HOME/.local/share/fnm:$PATH" && eval "`fnm env`" && fnm install v20.5.1 && fnm use v20.5.1 && npm -g i npm@latest
- name: Install Deno and Bun
run: npm install -g npm@latest; npm install -g bun deno-bin
- name: Run tests
run: zig build test
run: zig build test
42 changes: 11 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,18 @@ Kivi is a high-performance in-memory key-value database written in the Zig progr

> :warning: **Kivi is currently in development mode and not production-ready.**
## Latest benchmark:
```
JsMap:
┌─────────┬─────────────────┬────────────────────┬───────────────────┐
│ (index) │ totalLookupTime │ totalInsertionTime │ totalDeletionTime │
├─────────┼─────────────────┼────────────────────┼───────────────────┤
│ 0 │ '134.71 ms' │ '362.9 ms' │ '212.05 ms' │
│ 1 │ '140.82 ms' │ '149.01 ms' │ '217.53 ms' │
│ average │ '137.77 ms' │ '255.95 ms' │ '214.79 ms' │
└─────────┴─────────────────┴────────────────────┴───────────────────┘
## Latest benchmark
comparing Kivi to Javascript's builtin Map:
| Runtime | FFI | Lookup | Insertion | Deletion |
|---------|------|--------------|--------------|--------------|
| NodeJs | Napi | 2x slower | 1.2x faster | 1.9x slower |
| Deno | Napi | 1.47x slower | 2.48x slower | 1.44x slower |
| Deno | FFI | 6.89x slower | 4.07x slower | 7.73x slower |
| Bun | Napi | 1.06x faster | 1.23 faster | 1.08x slower |
| Bun | FFI | 1.64x slower | 1.3x slower | 1.97x slower |

Kivi:
┌─────────┬─────────────────┬────────────────────┬───────────────────┐
│ (index) │ totalLookupTime │ totalInsertionTime │ totalDeletionTime │
├─────────┼─────────────────┼────────────────────┼───────────────────┤
│ 0 │ '916.68 ms' │ '225.9 ms' │ '976.8 ms' │
│ 1 │ '907.01 ms' │ '217.65 ms' │ '1017.35 ms' │
│ average │ '911.84 ms' │ '221.78 ms' │ '997.08 ms' │
└─────────┴─────────────────┴────────────────────┴───────────────────┘
This table shows how much JsMap is faster than Kivi:
┌───────────┬─────────┐
│ (index) │ Values │
├───────────┼─────────┤
│ lookup │ '6.62x' │
│ insertion │ '0.85x' │
│ deletion │ '4.64x' │
└───────────┴─────────┘
```

## Code of conduct:
## Code of conduct
You can check our [code of conduct guidelines](CODE_OF_CONDUCT.md).

## License:
## License
Kivi is licensed under MIT. Head over to [LICENSE](LICENSE) for full description.
76 changes: 30 additions & 46 deletions bench/bench-with-builtin.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { Kivi } from "../src/drivers/js/index.js";
import { isBun, isDeno } from "../src/drivers/js/runtime.js";
import { generateFakeData } from "./faker/generate.js";
import { Buffer } from "node:buffer";

const repeatBenchmark = 2;
const data = generateFakeData();

const assert = (name, left, right) => {
if (!left.equals(right)) {
if (
Buffer.from(left, "utf8").subarray(0, right.length).toString() !==
right.toString()
) {
throw new Error(
`Assertion '${name}' failed! Left was '${left.toString()}' and right was '${right.toString()}'.`
);
}
};
const roundToTwoDecimal = (num) => +(Math.round(num + "e+2") + "e-2");

const benchmarkRemove = (data, o, keyidx) => {
const startingTime = performance.now();
for (const item of data) {
o.rm(item[keyidx]);
}
return performance.now() - startingTime;
};
const benchmarkDeletion = (data, o, keyidx) => {
const startingTime = performance.now();
for (const item of data) {
Expand Down Expand Up @@ -48,15 +46,13 @@ const wrapInHumanReadable = (value) => {
totalLookupTime: roundToTwoDecimal(value.totalLookupTime) + " ms",
totalInsertionTime: roundToTwoDecimal(value.totalInsertionTime) + " ms",
totalDeletionTime: roundToTwoDecimal(value.totalDeletionTime) + " ms",
totalRemoveTime: roundToTwoDecimal(value.totalRemoveTime) + " ms",
};
};
const formatLogResult = (value) => {
return {
totalLookupTime: value.lookupDuration,
totalInsertionTime: value.insertionDuration,
totalDeletionTime: value.deletionDuration,
totalRemoveTime: value.removeDuration,
};
};
const logResults = (name, durationArr, averageArg) => {
Expand All @@ -71,7 +67,6 @@ const logResults = (name, durationArr, averageArg) => {
totalLookupTime: average.totalLookupTime,
totalInsertionTime: average.totalInsertionTime,
totalDeletionTime: average.totalDeletionTime,
totalRemoveTime: average.totalRemoveTime,
});

console.log(`\n${name}:`);
Expand All @@ -80,36 +75,32 @@ const logResults = (name, durationArr, averageArg) => {
average: wrapInHumanReadable(average),
});
};
const logRatio = () => {
const logRatio = (index1, index2) => {
console.log(
`\n This table shows how much ${averageLogResult[0].name} is faster than ${averageLogResult[1].name}:`
`\n This table shows how much ${averageLogResult[index1].name} is faster than ${averageLogResult[index2].name}:`
);

console.table({
lookup:
roundToTwoDecimal(
averageLogResult[1].totalLookupTime /
averageLogResult[0].totalLookupTime
averageLogResult[index2].totalLookupTime /
averageLogResult[index1].totalLookupTime
) + "x",
insertion:
roundToTwoDecimal(
averageLogResult[1].totalInsertionTime /
averageLogResult[0].totalInsertionTime
averageLogResult[index2].totalInsertionTime /
averageLogResult[index1].totalInsertionTime
) + "x",
deletion:
roundToTwoDecimal(
averageLogResult[1].totalDeletionTime /
averageLogResult[0].totalDeletionTime
) + "x",
remove:
roundToTwoDecimal(
averageLogResult[1].totalRemoveTime /
averageLogResult[0].totalRemoveTime
averageLogResult[index2].totalDeletionTime /
averageLogResult[index1].totalDeletionTime
) + "x",
});
};

const builtinMapBenchmark = () => {
const name = "JsMap";
const durationArr = [];
let average = {
insertionDuration: 0,
Expand All @@ -118,17 +109,14 @@ const builtinMapBenchmark = () => {
};
for (let i = 0; i < repeatBenchmark; i++) {
let o = {
name: "JsMap",
name,
map: new Map(),
get: function (k) {
return this.map.get(k);
},
set: function (k, v) {
return this.map.set(k, v);
},
rm: function (k) {
this.map.delete(k);
},
del: function (k) {
const v = this.map.get(k);
this.map.delete(k);
Expand All @@ -141,34 +129,31 @@ const builtinMapBenchmark = () => {
const insertionDuration = benchmarkInsertion(data, o, "key");
const lookupDuration = benchmarkLookup(data, o, "key");
const deletionDuration = benchmarkDeletion(data, o, "key");
const removeDuration = benchmarkRemove(data, o, "key");
o.destroy();
durationArr.push({
iteration: i,
insertionDuration,
lookupDuration,
deletionDuration,
removeDuration,
});
if (average.insertionDuration === 0) {
average = {
insertionDuration,
lookupDuration,
deletionDuration,
removeDuration,
};
} else {
average = {
insertionDuration: (average.insertionDuration + insertionDuration) / 2,
lookupDuration: (average.lookupDuration + lookupDuration) / 2,
deletionDuration: (average.deletionDuration + deletionDuration) / 2,
removeDuration: (average.removeDuration + removeDuration) / 2,
};
}
}
logResults("JsMap", durationArr, average);
logResults(name, durationArr, average);
};
const kiviBenchmark = () => {
const kiviBenchmark = (config) => {
const name = "Kivi " + (config.forceUseRuntimeFFI ? "FFI" : "Napi");
const durationArr = [];
let average = {
insertionDuration: 0,
Expand All @@ -177,8 +162,8 @@ const kiviBenchmark = () => {
};
for (let i = 0; i < repeatBenchmark; i++) {
let o = {
name: "Kivi",
map: new Kivi(),
name,
map: new Kivi(config),
get: function (k) {
return this.map.get(k);
},
Expand All @@ -188,45 +173,44 @@ const kiviBenchmark = () => {
del: function (k) {
return this.map.del(k);
},
rm: function (k) {
this.map.rm(k);
},
destroy: function () {
return this.map.destroy();
},
};
const insertionDuration = benchmarkInsertion(data, o, "kyb");
const lookupDuration = benchmarkLookup(data, o, "kyb");
const deletionDuration = benchmarkDeletion(data, o, "kyb");
const removeDuration = benchmarkRemove(data, o, "kyb");
o.destroy();
durationArr.push({
iteration: i,
insertionDuration,
lookupDuration,
deletionDuration,
removeDuration,
});
if (average.insertionDuration === 0) {
average = {
insertionDuration,
lookupDuration,
deletionDuration,
removeDuration,
};
} else {
average = {
insertionDuration: (average.insertionDuration + insertionDuration) / 2,
lookupDuration: (average.lookupDuration + lookupDuration) / 2,
deletionDuration: (average.deletionDuration + deletionDuration) / 2,
removeDuration: (average.removeDuration + removeDuration) / 2,
};
}
}
logResults("Kivi", durationArr, average);
logResults(name, durationArr, average);
};

builtinMapBenchmark();
kiviBenchmark();

logRatio();
kiviBenchmark({ forceUseRuntimeFFI: false });
if (isDeno() || isBun()) {
kiviBenchmark({ forceUseRuntimeFFI: true });
logRatio(0, 1);
logRatio(0, 2);
} else {
logRatio(0, 1);
}
2 changes: 2 additions & 0 deletions bench/faker/generate.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Buffer } from "node:buffer";

const count = 1_000_000;

const genRandomNum = (min, max) => {
Expand Down
2 changes: 1 addition & 1 deletion bench/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"nodejs-bench": "node bench-with-builtin.js",
"bun-bench": " bun run bench-with-builtin.js",
"deno-bench": "deno run --unstable --allow-all --v8-flags='--max-heap-size=2000,--max-old-space-size=2000' bench-with-builtin.js"
"deno-bench": "deno run --unstable --allow-all bench-with-builtin.js"
},
"author": "",
"license": "ISC",
Expand Down
22 changes: 7 additions & 15 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,16 @@ inline fn run_npm_command(
dependency_steps: anytype,
runner_step: *std.Build.Step,
) void {
var last_step: ?std.Build.Step = null;
inline for (commands, 0..) |command, idx| {
const syscommand = b.addSystemCommand(&[3][]const u8{ "pnpm", "run", command });
syscommand.cwd = get_lazypath(dir);

if (last_step) |step| {
var mutable_step = step;
syscommand.step.dependOn(&mutable_step);
} else {
inline for (dependency_steps) |dependency_step| {
syscommand.step.dependOn(dependency_step);
}
inline for (dependency_steps) |dependency_step| {
syscommand.step.dependOn(dependency_step);
}
if (idx == commands.len - 1) {
runner_step.dependOn(&syscommand.step);
}

last_step = syscommand.step;
}
}

Expand Down Expand Up @@ -225,8 +217,8 @@ pub fn build(b: *std.Build) !void {
"src/drivers/js",
.{
"nodejs-test",
// "deno-test",
// "bun-test",
"deno-test",
"bun-test",
},
.{ drivers_build_step, ffi_tests_step },
test_step,
Expand All @@ -239,10 +231,10 @@ pub fn build(b: *std.Build) !void {
"bench",
.{
"nodejs-bench",
// "deno-bench",
// "bun-bench",
"deno-bench",
"bun-bench",
},
.{drivers_build_step},
.{ core_build_step, drivers_build_step },
bench_step,
);
}
13 changes: 8 additions & 5 deletions src/core/Kivi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ mem_allocator: std.mem.Allocator,

const Kivi = @This();

inline fn stringcpy(dest: []u8, src: []const u8) void {
inline fn stringcpy(dest: []u8, src: []const u8) !void {
if (dest.len < src.len) {
return error.SmallDestMemory;
}
@memcpy(dest[0..src.len], src);
}

Expand All @@ -44,8 +47,8 @@ pub fn putEntry(self: *Kivi, key: []u8, value: []u8) !void {
pub fn set(self: *Kivi, key: []const u8, value: []const u8) !usize {
const key_slice = try self.reserve_key(key.len);
const value_slice = try self.reserve_value(value.len);
stringcpy(key_slice, key);
stringcpy(value_slice, value);
try stringcpy(key_slice, key);
try stringcpy(value_slice, value);

try self.putEntry(key_slice, value_slice);

Expand All @@ -63,7 +66,7 @@ pub fn get(self: *const Kivi, key: []const u8, value: ?[]u8) !usize {
const value_slice = try self.get_slice(key);

if (value != null) {
stringcpy(value.?, value_slice);
try stringcpy(value.?, value_slice);
}

return value_slice.len;
Expand All @@ -84,7 +87,7 @@ pub fn del(self: *Kivi, key: []const u8, value: ?[]u8) !usize {
const value_slice_len = value_slice.len;

if (value != null) {
stringcpy(value.?, value_slice);
try stringcpy(value.?, value_slice);
}

self.del_value(value_slice);
Expand Down
Loading

0 comments on commit f5af0e4

Please sign in to comment.