koffi 1.3.5 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CMakeLists.txt +1 -1
  2. package/ChangeLog.md +36 -0
  3. package/benchmark/atoi_koffi.js +3 -4
  4. package/benchmark/atoi_napi.js +2 -3
  5. package/benchmark/atoi_node_ffi.js +3 -4
  6. package/benchmark/raylib_cc.cc +3 -4
  7. package/benchmark/raylib_cc.js +31 -0
  8. package/benchmark/raylib_koffi.js +8 -9
  9. package/benchmark/raylib_node_ffi.js +4 -5
  10. package/benchmark/raylib_node_raylib.js +4 -5
  11. package/build/qemu/1.3.8/koffi_darwin_arm64.tar.gz +0 -0
  12. package/build/qemu/1.3.8/koffi_darwin_x64.tar.gz +0 -0
  13. package/build/qemu/1.3.8/koffi_freebsd_arm64.tar.gz +0 -0
  14. package/build/qemu/1.3.8/koffi_freebsd_ia32.tar.gz +0 -0
  15. package/build/qemu/1.3.8/koffi_freebsd_x64.tar.gz +0 -0
  16. package/build/qemu/1.3.8/koffi_linux_arm32hf.tar.gz +0 -0
  17. package/build/qemu/1.3.8/koffi_linux_arm64.tar.gz +0 -0
  18. package/build/qemu/1.3.8/koffi_linux_ia32.tar.gz +0 -0
  19. package/build/qemu/1.3.8/koffi_linux_riscv64hf64.tar.gz +0 -0
  20. package/build/qemu/1.3.8/koffi_linux_x64.tar.gz +0 -0
  21. package/build/qemu/1.3.8/koffi_openbsd_ia32.tar.gz +0 -0
  22. package/build/qemu/1.3.8/koffi_openbsd_x64.tar.gz +0 -0
  23. package/build/qemu/1.3.8/koffi_win32_arm64.tar.gz +0 -0
  24. package/build/qemu/1.3.8/koffi_win32_ia32.tar.gz +0 -0
  25. package/build/qemu/1.3.8/koffi_win32_x64.tar.gz +0 -0
  26. package/doc/_static/perf_linux_20220627.png +0 -0
  27. package/doc/_static/perf_linux_20220628.png +0 -0
  28. package/doc/_static/perf_windows_20220627.png +0 -0
  29. package/doc/_static/perf_windows_20220628.png +0 -0
  30. package/doc/benchmarks.md +78 -58
  31. package/doc/benchmarks.xlsx +0 -0
  32. package/doc/conf.py +1 -1
  33. package/doc/contribute.md +8 -11
  34. package/doc/dist/html/_sources/benchmarks.md.txt +78 -58
  35. package/doc/dist/html/_sources/contribute.md.txt +8 -11
  36. package/doc/dist/html/_sources/functions.md.txt +9 -8
  37. package/doc/dist/html/_sources/index.rst.txt +3 -0
  38. package/doc/dist/html/_sources/platforms.md.txt +17 -5
  39. package/doc/dist/html/_sources/start.md.txt +14 -3
  40. package/doc/dist/html/_sources/types.md.txt +15 -11
  41. package/doc/dist/html/_static/basic.css +12 -14
  42. package/doc/dist/html/_static/perf_linux_20220627.png +0 -0
  43. package/doc/dist/html/_static/perf_linux_20220628.png +0 -0
  44. package/doc/dist/html/_static/perf_windows_20220627.png +0 -0
  45. package/doc/dist/html/_static/perf_windows_20220628.png +0 -0
  46. package/doc/dist/html/benchmarks.html +148 -159
  47. package/doc/dist/html/changes.html +44 -2
  48. package/doc/dist/html/contribute.html +30 -33
  49. package/doc/dist/html/functions.html +19 -18
  50. package/doc/dist/html/genindex.html +2 -2
  51. package/doc/dist/html/index.html +19 -10
  52. package/doc/dist/html/memory.html +2 -2
  53. package/doc/dist/html/objects.inv +0 -0
  54. package/doc/dist/html/platforms.html +44 -10
  55. package/doc/dist/html/search.html +2 -2
  56. package/doc/dist/html/searchindex.js +1 -1
  57. package/doc/dist/html/start.html +25 -12
  58. package/doc/dist/html/types.html +31 -11
  59. package/doc/functions.md +9 -8
  60. package/doc/index.rst +3 -0
  61. package/doc/platforms.md +17 -5
  62. package/doc/start.md +14 -3
  63. package/doc/types.md +15 -11
  64. package/package.json +7 -4
  65. package/qemu/qemu.js +30 -19
  66. package/qemu/registry/machines.json +19 -19
  67. package/qemu/registry/sha256sum.txt +5 -5
  68. package/src/abi_arm32.cc +9 -2
  69. package/src/abi_arm32_fwd.S +7 -7
  70. package/src/abi_arm64.cc +9 -2
  71. package/src/abi_arm64_fwd.S +11 -7
  72. package/src/abi_arm64_fwd.asm +7 -7
  73. package/src/abi_riscv64.cc +9 -2
  74. package/src/abi_riscv64_fwd.S +11 -11
  75. package/src/abi_x64_sysv.cc +9 -2
  76. package/src/abi_x64_sysv_fwd.S +11 -11
  77. package/src/abi_x64_win.cc +9 -2
  78. package/src/abi_x64_win_fwd.asm +7 -7
  79. package/src/abi_x86.cc +9 -2
  80. package/src/abi_x86_fwd.S +3 -0
  81. package/src/abi_x86_fwd.asm +3 -0
  82. package/src/call.cc +20 -10
  83. package/src/ffi.cc +17 -8
  84. package/src/ffi.hh +4 -3
  85. package/src/util.cc +1 -1
  86. package/test/async.js +1 -1
  87. package/test/callbacks.js +25 -2
  88. package/test/misc.c +57 -2
  89. package/test/raylib.js +4 -4
  90. package/test/sqlite.js +5 -5
  91. package/test/sync.js +22 -7
  92. package/build/qemu/1.3.5/koffi_darwin_arm64.tar.gz +0 -0
  93. package/build/qemu/1.3.5/koffi_darwin_x64.tar.gz +0 -0
  94. package/build/qemu/1.3.5/koffi_freebsd_arm64.tar.gz +0 -0
  95. package/build/qemu/1.3.5/koffi_freebsd_ia32.tar.gz +0 -0
  96. package/build/qemu/1.3.5/koffi_freebsd_x64.tar.gz +0 -0
  97. package/build/qemu/1.3.5/koffi_linux_arm32hf.tar.gz +0 -0
  98. package/build/qemu/1.3.5/koffi_linux_arm64.tar.gz +0 -0
  99. package/build/qemu/1.3.5/koffi_linux_ia32.tar.gz +0 -0
  100. package/build/qemu/1.3.5/koffi_linux_riscv64hf64.tar.gz +0 -0
  101. package/build/qemu/1.3.5/koffi_linux_x64.tar.gz +0 -0
  102. package/build/qemu/1.3.5/koffi_openbsd_ia32.tar.gz +0 -0
  103. package/build/qemu/1.3.5/koffi_openbsd_x64.tar.gz +0 -0
  104. package/build/qemu/1.3.5/koffi_win32_arm64.tar.gz +0 -0
  105. package/build/qemu/1.3.5/koffi_win32_ia32.tar.gz +0 -0
  106. package/build/qemu/1.3.5/koffi_win32_x64.tar.gz +0 -0
package/CMakeLists.txt CHANGED
@@ -110,7 +110,7 @@ if(WIN32)
110
110
  target_compile_definitions(koffi PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
111
111
  target_link_libraries(koffi PRIVATE ws2_32)
112
112
  endif()
113
- if(NOT MSVC)
113
+ if(NOT MSVC OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang")
114
114
  # Restore C/C++ compiler sanity
115
115
 
116
116
  target_compile_options(koffi PRIVATE -fno-exceptions -fno-strict-aliasing -fwrapv
package/ChangeLog.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## Koffi 1.3.8
4
+
5
+ **Main changes:**
6
+
7
+ - Prevent callback reuse beyond FFI call
8
+ - Add BTI support for AAarch64 platforms (except Windows)
9
+
10
+ **Other changes:**
11
+
12
+ - Fix and harmonize a few error messages
13
+
14
+ ## Koffi 1.3.7
15
+
16
+ **Main fixes:**
17
+
18
+ - Fix crash when using callbacks inside structs
19
+ - Support for null strings in record members
20
+
21
+ **Other changes:**
22
+
23
+ - Add intptr_t and uintptr_t primitive types
24
+ - Add str/str16 type aliases for string/string16
25
+ - Various documentation fixes and improvements
26
+
27
+ ## Koffi 1.3.6
28
+
29
+ **Main fixes:**
30
+
31
+ - Fix install error with Node < 15 on Windows (build system bug)
32
+
33
+ **Other changes:**
34
+
35
+ - Detect incompatible Node.js versions when installing Koffi
36
+ - Prebuild with Clang for Windows x64 and Linux x64 binaries
37
+ - Various documentation improvements
38
+
3
39
  ## Koffi 1.3.5
4
40
 
5
41
  **Main changes:**
@@ -35,11 +35,10 @@ function main() {
35
35
  if (iterations < 1)
36
36
  throw new Error('Value must be positive');
37
37
  }
38
- console.log('Iterations:', iterations);
39
38
 
40
39
  let lib = koffi.load(process.platform == 'win32' ? 'msvcrt.dll' : null);
41
40
 
42
- const atoi = lib.cdecl('atoi', 'int', ['string']);
41
+ const atoi = lib.cdecl('atoi', 'int', ['str']);
43
42
 
44
43
  let start = performance.now();
45
44
 
@@ -47,6 +46,6 @@ function main() {
47
46
  sum += atoi(strings[i % strings.length]);
48
47
  }
49
48
 
50
- let time = performance.now()- start;
51
- console.log('Time:', (time / 1000.0).toFixed(2) + 's');
49
+ let time = performance.now() - start;
50
+ console.log(JSON.stringify({ iterations: iterations, time: Math.round(time) }));
52
51
  }
@@ -35,7 +35,6 @@ function main() {
35
35
  if (iterations < 1)
36
36
  throw new Error('Value must be positive');
37
37
  }
38
- console.log('Iterations:', iterations);
39
38
 
40
39
  let start = performance.now();
41
40
 
@@ -43,6 +42,6 @@ function main() {
43
42
  sum += atoi.atoi(strings[i % strings.length]);
44
43
  }
45
44
 
46
- let time = performance.now()- start;
47
- console.log('Time:', (time / 1000.0).toFixed(2) + 's');
45
+ let time = performance.now() - start;
46
+ console.log(JSON.stringify({ iterations: iterations, time: Math.round(time) }));
48
47
  }
@@ -28,7 +28,7 @@ let sum = 0;
28
28
  main();
29
29
 
30
30
  async function main() {
31
- let iterations = 20000000;
31
+ let iterations = 200000;
32
32
 
33
33
  if (process.argv.length >= 3) {
34
34
  iterations = parseInt(process.argv[2], 10);
@@ -37,7 +37,6 @@ async function main() {
37
37
  if (iterations < 1)
38
38
  throw new Error('Value must be positive');
39
39
  }
40
- console.log('Iterations:', iterations);
41
40
 
42
41
  const lib = ffi.Library(process.platform == 'win32' ? 'msvcrt.dll' : null, {
43
42
  atoi: ['int', ['string']]
@@ -52,6 +51,6 @@ async function main() {
52
51
  sum += lib.atoi(strings[i % strings.length]);
53
52
  }
54
53
 
55
- let time = performance.now()- start;
56
- console.log('Time:', (time / 1000.0).toFixed(2) + 's');
54
+ let time = performance.now() - start;
55
+ console.log(JSON.stringify({ iterations: iterations, time: Math.round(time) }));
57
56
  }
@@ -18,13 +18,12 @@ namespace RG {
18
18
 
19
19
  int Main(int argc, char **argv)
20
20
  {
21
- int iterations = 100;
21
+ int iterations = 360000;
22
22
 
23
23
  if (argc >= 2) {
24
24
  if (!ParseInt(argv[1], &iterations))
25
25
  return 1;
26
26
  }
27
- LogInfo("Iterations: %1", iterations);
28
27
 
29
28
  // We need to call InitWindow before using anything else (such as fonts)
30
29
  SetTraceLogLevel(LOG_WARNING);
@@ -36,7 +35,7 @@ int Main(int argc, char **argv)
36
35
 
37
36
  int64_t start = GetMonotonicTime();
38
37
 
39
- for (int i = 0; i < iterations; i++) {
38
+ for (int i = 0; i < iterations; i += 3600) {
40
39
  ImageClearBackground(&img, Color { 0, 0, 0, 255 });
41
40
 
42
41
  for (int j = 0; j < 3600; j++) {
@@ -60,7 +59,7 @@ int Main(int argc, char **argv)
60
59
  }
61
60
 
62
61
  int64_t time = GetMonotonicTime() - start;
63
- LogInfo("Time: %1s", FmtDouble((double)time / 1000.0, 2));
62
+ PrintLn("{\"iterations\": %1,\"time\": %2}", iterations, time);
64
63
 
65
64
  return 0;
66
65
  }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This program is free software: you can redistribute it and/or modify
4
+ // it under the terms of the GNU Affero General Public License as published by
5
+ // the Free Software Foundation, either version 3 of the License, or
6
+ // (at your option) any later version.
7
+ //
8
+ // This program is distributed in the hope that it will be useful,
9
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ // GNU Affero General Public License for more details.
12
+ //
13
+ // You should have received a copy of the GNU Affero General Public License
14
+ // along with this program. If not, see https://www.gnu.org/licenses/.
15
+
16
+ const { spawnSync } = require('child_process');
17
+ const path = require('path');
18
+
19
+ main();
20
+
21
+ function main() {
22
+ let filename = path.join(__dirname, 'build/raylib_cc' + (process.platform == 'win32' ? '.exe' : ''));
23
+ let proc = spawnSync(filename, process.argv.slice(2), { stdio: 'inherit' });
24
+
25
+ if (proc.status == null) {
26
+ console.error(proc.error);
27
+ process.exit(1);
28
+ }
29
+
30
+ process.exit(proc.status);
31
+ }
@@ -71,7 +71,7 @@ const Font = koffi.struct('Font', {
71
71
  main();
72
72
 
73
73
  function main() {
74
- let iterations = 100;
74
+ let iterations = 360000;
75
75
 
76
76
  if (process.argv.length >= 3) {
77
77
  iterations = parseInt(process.argv[2], 10);
@@ -80,20 +80,19 @@ function main() {
80
80
  if (iterations < 1)
81
81
  throw new Error('Value must be positive');
82
82
  }
83
- console.log('Iterations:', iterations);
84
83
 
85
84
  let lib_filename = path.dirname(__filename) + '/build/raylib' + koffi.extension;
86
85
  let lib = koffi.load(lib_filename);
87
86
 
88
- const InitWindow = lib.cdecl('InitWindow', 'void', ['int', 'int', 'string']);
87
+ const InitWindow = lib.cdecl('InitWindow', 'void', ['int', 'int', 'str']);
89
88
  const SetTraceLogLevel = lib.cdecl('SetTraceLogLevel', 'void', ['int']);
90
89
  const SetWindowState = lib.cdecl('SetWindowState', 'void', ['uint']);
91
90
  const GenImageColor = lib.cdecl('GenImageColor', Image, ['int', 'int', Color]);
92
91
  const GetFontDefault = lib.cdecl('GetFontDefault', Font, []);
93
- const MeasureTextEx = lib.cdecl('MeasureTextEx', Vector2, [Font, 'string', 'float', 'float']);
92
+ const MeasureTextEx = lib.cdecl('MeasureTextEx', Vector2, [Font, 'str', 'float', 'float']);
94
93
  const ImageClearBackground = lib.cdecl('ImageClearBackground', 'void', [koffi.pointer(Image), Color]);
95
- const ImageDrawTextEx = lib.cdecl('ImageDrawTextEx', 'void', [koffi.pointer(Image), Font, 'string', Vector2, 'float', 'float', Color]);
96
- const ExportImage = lib.cdecl('ExportImage', 'bool', [Image, 'string']);
94
+ const ImageDrawTextEx = lib.cdecl('ImageDrawTextEx', 'void', [koffi.pointer(Image), Font, 'str', Vector2, 'float', 'float', Color]);
95
+ const ExportImage = lib.cdecl('ExportImage', 'bool', [Image, 'str']);
97
96
 
98
97
  // We need to call InitWindow before using anything else (such as fonts)
99
98
  SetTraceLogLevel(4); // Warnings
@@ -105,7 +104,7 @@ function main() {
105
104
 
106
105
  let start = performance.now();
107
106
 
108
- for (let i = 0; i < iterations; i++) {
107
+ for (let i = 0; i < iterations; i += 3600) {
109
108
  ImageClearBackground(img, { r: 0, g: 0, b: 0, a: 255 });
110
109
 
111
110
  for (let j = 0; j < 3600; j++) {
@@ -128,6 +127,6 @@ function main() {
128
127
  }
129
128
  }
130
129
 
131
- let time = performance.now()- start;
132
- console.log('Time:', (time / 1000.0).toFixed(2) + 's');
130
+ let time = performance.now() - start;
131
+ console.log(JSON.stringify({ iterations: iterations, time: Math.round(time) }));
133
132
  }
@@ -87,7 +87,7 @@ const Font = struct({
87
87
  main();
88
88
 
89
89
  function main() {
90
- let iterations = 100;
90
+ let iterations = 180000;
91
91
 
92
92
  if (process.argv.length >= 3) {
93
93
  iterations = parseInt(process.argv[2], 10);
@@ -96,7 +96,6 @@ function main() {
96
96
  if (iterations < 1)
97
97
  throw new Error('Value must be positive');
98
98
  }
99
- console.log('Iterations:', iterations);
100
99
 
101
100
  let lib_filename = path.dirname(__filename) + '/build/raylib' + koffi.extension;
102
101
 
@@ -123,7 +122,7 @@ function main() {
123
122
 
124
123
  let start = performance.now();
125
124
 
126
- for (let i = 0; i < iterations; i++) {
125
+ for (let i = 0; i < iterations; i += 3600) {
127
126
  r.ImageClearBackground(imgp, new Color({ r: 0, g: 0, b: 0, a: 255 }));
128
127
 
129
128
  for (let j = 0; j < 3600; j++) {
@@ -146,6 +145,6 @@ function main() {
146
145
  }
147
146
  }
148
147
 
149
- let time = performance.now()- start;
150
- console.log('Time:', (time / 1000.0).toFixed(2) + 's');
148
+ let time = performance.now() - start;
149
+ console.log(JSON.stringify({ iterations: iterations, time: Math.round(time) }));
151
150
  }
@@ -18,7 +18,7 @@ const r = require('raylib');
18
18
  main();
19
19
 
20
20
  function main() {
21
- let iterations = 100;
21
+ let iterations = 360000;
22
22
 
23
23
  if (process.argv.length >= 3) {
24
24
  iterations = parseInt(process.argv[2], 10);
@@ -27,7 +27,6 @@ function main() {
27
27
  if (iterations < 1)
28
28
  throw new Error('Value must be positive');
29
29
  }
30
- console.log('Iterations:', iterations);
31
30
 
32
31
  // We need to call InitWindow before using anything else (such as fonts)
33
32
  r.SetTraceLogLevel(4); // Warnings
@@ -39,7 +38,7 @@ function main() {
39
38
 
40
39
  let start = performance.now();
41
40
 
42
- for (let i = 0; i < iterations; i++) {
41
+ for (let i = 0; i < iterations; i += 3600) {
43
42
  r.ImageClearBackground(img, { r: 0, g: 0, b: 0, a: 255 });
44
43
 
45
44
  for (let j = 0; j < 3600; j++) {
@@ -62,6 +61,6 @@ function main() {
62
61
  }
63
62
  }
64
63
 
65
- let time = performance.now()- start;
66
- console.log('Time:', (time / 1000.0).toFixed(2) + 's');
64
+ let time = performance.now() - start;
65
+ console.log(JSON.stringify({ iterations: iterations, time: Math.round(time) }));
67
66
  }
package/doc/benchmarks.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Benchmarks
2
2
 
3
+ ## Overview
4
+
3
5
  Here is a quick overview of the execution time of Koffi calls on three benchmarks, where it is compared to a theoretical ideal FFI implementation (approximated with pre-compiled static N-API glue code):
4
6
 
5
7
  - The first benchmark is based on `rand()` calls
@@ -8,14 +10,18 @@ Here is a quick overview of the execution time of Koffi calls on three benchmark
8
10
 
9
11
  <table style="margin: 0 auto;">
10
12
  <tr>
11
- <td><a href="_static/perf_linux_20220623_2.png" target="_blank"><img src="_static/perf_linux_20220623_2.png" alt="Linux performance" style="width: 350px;"/></a></td>
12
- <td><a href="_static/perf_windows_20220623_2.png" target="_blank"><img src="_static/perf_windows_20220623_2.png" alt="Windows performance" style="width: 350px;"/></a></td>
13
+ <td><a href="_static/perf_linux_20220628.png" target="_blank"><img src="_static/perf_linux_20220628.png" alt="Linux performance" style="width: 350px;"/></a></td>
14
+ <td><a href="_static/perf_windows_20220628.png" target="_blank"><img src="_static/perf_windows_20220628.png" alt="Windows performance" style="width: 350px;"/></a></td>
13
15
  </tr>
14
16
  </table>
15
17
 
16
18
  These results are detailed and explained below, and compared to node-ffi/node-ffi-napi.
17
19
 
18
- ## rand results
20
+ ## Linux x86_64
21
+
22
+ The results presented below were measured on my x86_64 Linux machine (Intel® Core™ i5-4460).
23
+
24
+ ### rand results
19
25
 
20
26
  This test is based around repeated calls to a simple standard C function atoi, and has three implementations:
21
27
 
@@ -23,95 +29,109 @@ This test is based around repeated calls to a simple standard C function atoi, a
23
29
  - the second one calls atoi through Koffi
24
30
  - the third one uses the official Node.js FFI implementation, node-ffi-napi
25
31
 
32
+ Benchmark | Iteration time | Relative performance | Overhead
33
+ ------------- | -------------- | -------------------- | --------
34
+ rand_napi | 644 ns | x1.00 | (ref)
35
+ rand_koffi | 950 ns | x0.68 | +48%
36
+ rand_node_ffi | 30350 ns | x0.02 | +4613%
37
+
26
38
  Because rand is a pretty small function, the FFI overhead is clearly visible.
27
39
 
28
- ### Linux x86_64
40
+ ### atoi results
29
41
 
30
- The results below were measured on my x86_64 Linux machine (AMD® Ryzen™ 7 4700U):
42
+ This test is similar to the rand one, but it is based on atoi, which takes a string parameter. Javascript (V8) to C string conversion is relatively slow and heavy.
31
43
 
32
- Benchmark | Iterations | Total time | Relative performance | Overhead
33
- ------------- | ---------- | ----------- | -------------------- | ----------
34
- rand_napi | 20000000 | 1.44s | (baseline) | (baseline)
35
- rand_koffi | 20000000 | 2.60s | x0.55 | +81%
36
- rand_node_ffi | 20000000 | 107.58s | x0.01 | +7400%
44
+ Benchmark | Iteration time | Relative performance | Overhead
45
+ ------------- | -------------- | -------------------- | --------
46
+ atoi_napi | 1104 ns | x1.00 | (ref)
47
+ atoi_koffi | 1778 ns | x0.62 | +61%
48
+ atoi_node_ffi | 125300 ns | x0.009 | +11250%
37
49
 
38
- ### Windows x86_64
50
+ Because atoi is a pretty small function, the FFI overhead is clearly visible.
39
51
 
40
- The results below were measured on my x86_64 Windows machine (Intel® Core™ i5-4460):
52
+ ### Raylib results
53
+
54
+ This benchmark uses the CPU-based image drawing functions in Raylib. The calls are much heavier than in the atoi benchmark, thus the FFI overhead is reduced. In this implementation, Koffi is compared to:
41
55
 
42
- Benchmark | Iterations | Total time | Relative performance | Overhead
43
- ------------- | ---------- | ----------- | -------------------- | ----------
44
- rand_napi | 20000000 | 2.10s | (baseline) | (baseline)
45
- rand_koffi | 20000000 | 3.87s | x0.54 | +84%
46
- rand_node_ffi | 20000000 | 87.84s | x0.02 | +4100%
56
+ - Baseline: Full C++ version of the code (no JS)
57
+ - [node-raylib](https://github.com/RobLoach/node-raylib): This is a native wrapper implemented with N-API
47
58
 
48
- ## atoi results
59
+ Benchmark | Iteration time | Relative performance | Overhead
60
+ ------------------ | -------------- | -------------------- | --------
61
+ raylib_cc | 215.7 µs | x1.20 | -17%
62
+ raylib_node_raylib | 258.9 µs | x1.00 | (ref)
63
+ raylib_koffi | 311.6 µs | x0.83 | +20%
64
+ raylib_node_ffi | 928.4 µs | x0.28 | +259%
49
65
 
50
- This test is similar to the rand one, but it is based on atoi, which takes a string parameter. Javascript (V8) to C string conversion is relatively slow and heavy.
66
+ ## Windows x86_64
51
67
 
52
- Because rand is a pretty small function, the FFI overhead is clearly visible.
68
+ The results presented below were measured on my x86_64 Windows machine (Intel® Core™ i5-4460).
69
+
70
+ ### rand results
53
71
 
54
- ### Linux x86_64
72
+ This test is based around repeated calls to a simple standard C function atoi, and has three implementations:
55
73
 
56
- The results below were measured on my x86_64 Linux machine (AMD® Ryzen™ 7 4700U):
74
+ - the first one is the reference, it calls atoi through an N-API module, and is close to the theoretical limit of a perfect (no overhead) Node.js > C FFI implementation (pre-compiled static glue code)
75
+ - the second one calls atoi through Koffi
76
+ - the third one uses the official Node.js FFI implementation, node-ffi-napi
57
77
 
58
- Benchmark | Iterations | Total time | Relative performance | Overhead
59
- ------------- | ---------- | ----------- | -------------------- | ----------
60
- atoi_napi | 20000000 | 2.97s | (baseline) | (baseline)
61
- atoi_koffi | 20000000 | 5.07s | x0.58 | +71%
62
- atoi_node_ffi | 20000000 | 693.16s | x0.005 | +23000%
78
+ Benchmark | Iteration time | Relative performance | Overhead
79
+ ------------- | -------------- | -------------------- | --------
80
+ rand_napi | 965 ns | x1.00 | (ref)
81
+ rand_koffi | 1248 ns | x0.77 | +29%
82
+ rand_node_ffi | 41500 ns | x0.02 | +4203%
63
83
 
64
- ### Windows x86_64
84
+ Because rand is a pretty small function, the FFI overhead is clearly visible.
65
85
 
66
- The results below were measured on my x86_64 Windows machine (Intel® Core™ i5-4460):
86
+ ### atoi results
67
87
 
68
- Benchmark | Iterations | Total time | Relative performance | Overhead
69
- ------------- | ---------- | ----------- | -------------------- | ----------
70
- atoi_napi | 20000000 | 2.97s | (baseline) | (baseline)
71
- atoi_koffi | 20000000 | 5.91s | x0.50 | +99%
72
- atoi_node_ffi | 20000000 | 479.34s | x0.006 | +16000%
88
+ This test is similar to the rand one, but it is based on atoi, which takes a string parameter. Javascript (V8) to C string conversion is relatively slow and heavy.
73
89
 
74
- ## Raylib results
90
+ The results below were measured on my x86_64 Windows machine (Intel® Core™ i5-4460):
75
91
 
76
- This benchmark uses the CPU-based image drawing functions in Raylib. The calls are much heavier than in the atoi benchmark, thus the FFI overhead is reduced. In this implementation, Koffi is compared to:
92
+ Benchmark | Iteration time | Relative performance | Overhead
93
+ ------------- | -------------- | -------------------- | --------
94
+ atoi_napi | 1393 ns | x1.00 | (ref)
95
+ atoi_koffi | 2246 ns | x0.62 | +61%
96
+ atoi_node_ffi | 157550 ns | x0.009 | +11210%
77
97
 
78
- - Baseline: Full C++ version of the code (no JS)
79
- - [node-raylib](https://github.com/RobLoach/node-raylib): This is a native wrapper implemented with N-API
98
+ Because atoi is a pretty small function, the FFI overhead is clearly visible.
80
99
 
81
- ### Linux x86_64
100
+ ### Raylib results
82
101
 
83
- The results below were measured on my x86_64 Linux machine (AMD® Ryzen™ 7 4700U):
102
+ This benchmark uses the CPU-based image drawing functions in Raylib. The calls are much heavier than in the atoi benchmark, thus the FFI overhead is reduced. In this implementation, Koffi is compared to:
84
103
 
85
- Benchmark | Iterations | Total time | Relative performance | Overhead
86
- --------------- | ---------- | ----------- | -------------------- | ----------
87
- raylib_cc | 100 | 9.31s | x1.17 | -15%
88
- raylib_node_raylib | 100 | 10.90s | (baseline) | (baseline)
89
- raylib_koffi | 100 | 12.86s | x0.84 | +18%
90
- raylib_node_ffi | 100 | 35.76s | x0.30 | +228%
104
+ - [node-raylib](https://github.com/RobLoach/node-raylib) (baseline): This is a native wrapper implemented with N-API
105
+ - raylib_cc: C++ implementation of the benchmark, without any Javascript
91
106
 
92
- ### Windows x86_64
107
+ Benchmark | Iteration time | Relative performance | Overhead
108
+ ------------------ | -------------- | -------------------- | --------
109
+ raylib_cc | 211.8 µs | x1.25 | -20%
110
+ raylib_node_raylib | 264.4 µs | x1.00 | (ref)
111
+ raylib_koffi | 318.9 µs | x0.83 | +21%
112
+ raylib_node_ffi | 1146.2 µs | x0.23 | +334%
93
113
 
94
- The results below were measured on my x86_64 Windows machine (Intel® Core™ i5-4460):
114
+ Please note that in order to get fair numbers for raylib_node_raylib, it was recompiled with clang-cl before running the benchmark with the following commands:
95
115
 
96
- Benchmark | Iterations | Total time | Relative performance | Overhead
97
- --------------- | ---------- | ----------- | -------------------- | ----------
98
- raylib_cc | 100 | 10.67s | x1.17 | -12%
99
- raylib_node_raylib | 100 | 12.05s | (baseline) | (baseline)
100
- raylib_koffi | 100 | 14.84s | x0.81 | +23%
101
- raylib_node_ffi | 100 | 44.63s | x0.27 | +270%
116
+ ```batch
117
+ cd node_modules\raylib
118
+ rmdir /S /Q bin build
119
+ npx cmake-js compile -t ClangCL
120
+ ```
102
121
 
103
122
  ## Running benchmarks
104
123
 
105
124
  Open a console, go to `koffi/benchmark` and run `../../cnoke/cnoke.js` (or `node ..\..\cnoke\cnoke.js` on Windows) before doing anything else.
106
125
 
126
+ Please note that all benchmark results are made with Clang-built binaries.
127
+
107
128
  ```sh
108
129
  cd koffi/benchmark
109
- node ../../cnoke/cnoke.js
130
+ node ../../cnoke/cnoke.js --prefer-clang
110
131
  ```
111
132
 
112
- Once this is done, you can execute each implementation, e.g. `build/raylib_cc` or `node ./atoi_koffi.js`. You can optionally define a custom number of iterations, e.g. `node ./atoi_koffi.js 10000000`.
133
+ Once everything is built and ready, run:
113
134
 
114
135
  ```sh
115
- node ./atoi_napi.js
116
- node ./atoi_koffi.js
136
+ node benchmark.js
117
137
  ```
Binary file
package/doc/conf.py CHANGED
@@ -58,4 +58,4 @@ myst_heading_anchors = 3
58
58
 
59
59
  myst_linkify_fuzzy_links = False
60
60
 
61
- myst_number_code_blocks = ['c', 'js']
61
+ myst_number_code_blocks = ['c', 'js', 'sh', 'batch']
package/doc/contribute.md CHANGED
@@ -69,7 +69,7 @@ Note that the machine disk content may change each time the machine runs, so the
69
69
  And now you can run the tests with:
70
70
 
71
71
  ```sh
72
- node qemu.js # Several options are available, use --help
72
+ node qemu.js test # Several options are available, use --help
73
73
  ```
74
74
 
75
75
  And be patient, this can be pretty slow for emulated machines. The Linux machines have and use ccache to build Koffi, so subsequent build steps will get much more tolerable.
@@ -78,8 +78,8 @@ By default, machines are started and stopped for each test. But you can start th
78
78
 
79
79
  ```sh
80
80
  node qemu.js start # Start the machines
81
- node qemu.js # Test (without shutting down)
82
- node qemu.js # Test again
81
+ node qemu.js test # Test (without shutting down)
82
+ node qemu.js test # Test again
83
83
  node qemu.js stop # Stop everything
84
84
  ```
85
85
 
@@ -109,18 +109,15 @@ node qemu.js info debian_x64
109
109
 
110
110
  ## Todo list
111
111
 
112
- After the release of version 1.3.0, the current priorities for the next major release are:
112
+ The following features and improvements are planned, not necessarily in that order:
113
113
 
114
- - Automate Windows/AArch64 (qemu) and macOS/AArch64 (how?) tests and builds
115
- - Create a real-world example, using several libraries (Raylib, SQLite, libsodium) to illustrate how to work with various C API styles
116
-
117
- The following features are also planned eventually, not necessarily in that order:
118
-
119
- - Optimize passing of structs and arrays, with separate HFA-specific helper functions
114
+ - Provide better ways to automatically deal with caller/heap-allocated memory (strings, etc.)
115
+ - Optimize passing of structs and arrays (avoid setting named properties one by one? separate HFA-specific helper functions?)
116
+ - Automate Windows/AArch64 (qemu) and macOS/AArch64 (how? ... thanks Apple) tests
117
+ - Create a real-world example, using several libraries (Raylib, SQLite, libsodium) to illustrate various C API styles
120
118
  - Add simple struct type parser
121
119
  - Add more ways to manually encode and decode various types to and from byte arrays
122
120
  - Add support for unions
123
- - Provide better ways to automatically deal with caller/heap-allocated memory (strings, etc.)
124
121
  - Port Koffi to PowerPC (POWER9+) ABI
125
122
  - Fix assembly unwind and CFI directives for better debugging experience
126
123