mirror of
https://github.com/lune-org/lune.git
synced 2025-04-03 18:10:54 +01:00
Add benchmark tests/ffi/benchmark/external_call (#243)
This commit is contained in:
parent
144f49a11d
commit
d42bfc9f63
15 changed files with 253 additions and 13 deletions
|
@ -23,6 +23,8 @@ See [tests/ffi](../../tests/ffi/README.md)
|
|||
|
||||
> For windows API
|
||||
|
||||
- Add varargs support
|
||||
|
||||
## Code structure
|
||||
|
||||
### /c
|
||||
|
|
|
@ -107,6 +107,7 @@ impl BoxData {
|
|||
}
|
||||
|
||||
// Get size of box
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
@ -121,18 +122,22 @@ impl Drop for BoxData {
|
|||
}
|
||||
|
||||
impl FfiData for BoxData {
|
||||
#[inline]
|
||||
fn check_inner_boundary(&self, offset: isize, size: usize) -> bool {
|
||||
if offset < 0 {
|
||||
return false;
|
||||
}
|
||||
self.size() - (offset as usize) >= size
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn get_inner_pointer(&self) -> *mut () {
|
||||
self.data.as_ptr().cast_mut().cast::<()>()
|
||||
}
|
||||
#[inline]
|
||||
fn is_readable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
#[inline]
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@ impl RefBounds {
|
|||
Self { above, below }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_unsized(&self) -> bool {
|
||||
self.above == usize::MAX && self.below == usize::MAX
|
||||
}
|
||||
|
||||
// Check boundary
|
||||
#[inline]
|
||||
pub fn check_boundary(&self, offset: isize) -> bool {
|
||||
if self.is_unsized() {
|
||||
return true;
|
||||
|
@ -39,6 +41,7 @@ impl RefBounds {
|
|||
|
||||
// Check boundary
|
||||
// Check required here
|
||||
#[inline]
|
||||
pub fn check_sized(&self, offset: isize, size: usize) -> bool {
|
||||
if self.is_unsized() {
|
||||
return true;
|
||||
|
@ -61,6 +64,7 @@ impl RefBounds {
|
|||
|
||||
// Calculate new bounds from bounds and offset
|
||||
// No boundary checking in here
|
||||
#[inline]
|
||||
pub fn offset(&self, offset: isize) -> Self {
|
||||
let sign = offset.signum();
|
||||
let offset_abs = offset.unsigned_abs();
|
||||
|
|
|
@ -127,15 +127,19 @@ impl Drop for RefData {
|
|||
}
|
||||
|
||||
impl FfiData for RefData {
|
||||
#[inline]
|
||||
fn check_inner_boundary(&self, offset: isize, size: usize) -> bool {
|
||||
self.boundary.check_sized(offset, size)
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn get_inner_pointer(&self) -> *mut () {
|
||||
**self.ptr
|
||||
}
|
||||
#[inline]
|
||||
fn is_readable(&self) -> bool {
|
||||
u8_test(self.flags, RefFlag::Readable.value())
|
||||
}
|
||||
#[inline]
|
||||
fn is_writable(&self) -> bool {
|
||||
u8_test(self.flags, RefFlag::Writable.value())
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
gcc for library compiling (for external-\*)
|
||||
|
||||
## Results
|
||||
## Test Results
|
||||
|
||||
**External tests**
|
||||
|
||||
|
@ -12,9 +12,7 @@ gcc for library compiling (for external-\*)
|
|||
- [x] [external_pointer](./external_pointer/init.luau)
|
||||
- [x] [external_print](./external_print/init.luau)
|
||||
- [x] [external_struct](./external_struct/init.luau)
|
||||
- [ ] [external_closure](./external_closure/init.luau)
|
||||
|
||||
> failed (segfault)
|
||||
- [x] [external_closure](./external_closure/init.luau)
|
||||
|
||||
**Luau-side**
|
||||
|
||||
|
@ -34,3 +32,47 @@ gcc for library compiling (for external-\*)
|
|||
- [ ] [cast](./cast)
|
||||
|
||||
> need assertion
|
||||
|
||||
## Benchmark Results
|
||||
|
||||
> Note: LuaJit's os.clock function returns process CPU time (used) which much smaller then Luau's os.clock output. In this benchmark, luau uses 'time.h' instead of os.clock. See [utility/proc_clock](./utility/proc_clock/init.luau)
|
||||
|
||||
### [benchmark/external_call](./benchmark/external_call/init.luau)
|
||||
|
||||
**Target external c function**
|
||||
|
||||
```c
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
bench_scale = 1000000
|
||||
|
||||
**Lune ffi call function**
|
||||
|
||||
> cargo run run tests/ffi/benchmark/external_call
|
||||
> cargo run --profile=release run tests/ffi/benchmark/external_call
|
||||
|
||||
Lune release target: 0.205127 (sec)
|
||||
Lune dev target: 1.556489 (sec)
|
||||
|
||||
**LuaJit ffi call function**
|
||||
|
||||
> luajit tests/ffi/benchmark/external_call/luajit.lua
|
||||
|
||||
LuaJIT 2.1.1727870382: 0.001682 (sec)
|
||||
flags = JIT ON SSE3 SSE4.1 BMI2 fold cse dce fwd dse narrow loop abc sink fuse
|
||||
|
||||
**Deno ffi call function**
|
||||
|
||||
> deno run --unstable-ffi --allow-ffi ./tests/ffi/benchmark/external_call/deno.ts
|
||||
|
||||
Deno 1.46.3: 0.006384 (sec)
|
||||
v8 = 12.9.202.5-rusty
|
||||
|
||||
**Sysinformation**
|
||||
|
||||
> CPU: AMD Ryzen 5 7600 (12) @ 5.1
|
||||
> MEM: 61898MiB 5600 MT/s
|
||||
> KERNEL: 6.8.12-2-pve (Proxmox VE 8.2.7 x86_64)
|
||||
|
|
23
tests/ffi/benchmark/external_call/deno.ts
Normal file
23
tests/ffi/benchmark/external_call/deno.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { libSuffix } from "../../utility/deno.ts";
|
||||
import { get_clock, get_offset } from "../../utility/proc_clock/deno.ts";
|
||||
|
||||
const library_file = "./tests/ffi/benchmark/external_call/lib."+libSuffix;
|
||||
let library = Deno.dlopen(library_file, {
|
||||
add: {
|
||||
parameters: ["i32", "i32"],
|
||||
result: "i32",
|
||||
},
|
||||
});
|
||||
|
||||
function bench_add(bench_size: number) {
|
||||
let add = library.symbols.add;
|
||||
let value = 0;
|
||||
const before = get_clock();
|
||||
for (let i=0; i<bench_size; i++) {
|
||||
value = add(value,1);
|
||||
}
|
||||
const after = get_clock();
|
||||
console.log(get_offset(before,after))
|
||||
}
|
||||
|
||||
bench_add(1000000);
|
35
tests/ffi/benchmark/external_call/init.luau
Normal file
35
tests/ffi/benchmark/external_call/init.luau
Normal file
|
@ -0,0 +1,35 @@
|
|||
local ffi = require("@lune/ffi")
|
||||
local c = ffi.c
|
||||
|
||||
local proc_clock = require("../../utility/proc_clock")
|
||||
local before, after = proc_clock.new_box()
|
||||
local get_clock = proc_clock.get_clock
|
||||
|
||||
local testdir = "./tests/ffi/benchmark/external_call"
|
||||
local compile = require("../../utility/compile")
|
||||
compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
|
||||
local lib = ffi.open(`{testdir}/lib.so`)
|
||||
|
||||
local function bench_add(bench_scale: number)
|
||||
local add_info = c.fn({ c.int, c.int }, c.int)
|
||||
|
||||
local add_callable = add_info:callable(lib:find("add"))
|
||||
|
||||
local a = c.int:box(0)
|
||||
local delta = c.int:box(1)
|
||||
|
||||
local a_ref = a:ref()
|
||||
local delta_ref = delta:ref()
|
||||
|
||||
get_clock(before)
|
||||
for i = 1, bench_scale do
|
||||
add_callable(a, a_ref, delta_ref)
|
||||
end
|
||||
get_clock(after)
|
||||
print(proc_clock.get_offset(before, after))
|
||||
|
||||
local result = c.int:readData(a)
|
||||
assert(result == bench_scale, `bench_add failed. result expected {bench_scale}, got {result}`)
|
||||
end
|
||||
|
||||
bench_add(1000000)
|
4
tests/ffi/benchmark/external_call/lib.c
Normal file
4
tests/ffi/benchmark/external_call/lib.c
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
27
tests/ffi/benchmark/external_call/luajit.lua
Normal file
27
tests/ffi/benchmark/external_call/luajit.lua
Normal file
|
@ -0,0 +1,27 @@
|
|||
--!nolint
|
||||
--!nocheck
|
||||
|
||||
local ffi = require("ffi")
|
||||
|
||||
local function bench_add(bench_scale)
|
||||
ffi.cdef([[
|
||||
int add(int a, int b);
|
||||
]])
|
||||
local lib = ffi.load("./tests/ffi/benchmark/external_call/lib.so")
|
||||
local add = lib.add
|
||||
local a = 0
|
||||
|
||||
local before = os.clock()
|
||||
for i = 1, bench_scale do
|
||||
a = add(a, 1)
|
||||
end
|
||||
local after = os.clock()
|
||||
|
||||
print(after - before)
|
||||
assert(
|
||||
a == bench_scale,
|
||||
string.format("bench_add failed. result expected %d, got %d", bench_scale, a)
|
||||
)
|
||||
end
|
||||
|
||||
bench_add(1000000)
|
|
@ -1,37 +1,36 @@
|
|||
local ffi = require("@lune/ffi")
|
||||
local c = ffi.c
|
||||
|
||||
local testdir = "./tests/ffi/external_closure"
|
||||
|
||||
local compile = require("../utility/compile")
|
||||
compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
|
||||
|
||||
local lib = ffi.open(`{testdir}/lib.so`)
|
||||
|
||||
local function test_closure()
|
||||
local callback_info = ffi.c.fn({ ffi.c.int, ffi.c.int }, ffi.c.int)
|
||||
local callback_info = c.fn({ c.int, c.int }, c.int)
|
||||
local callback_closure = callback_info:closure(function(ret, a, b)
|
||||
ffi.c.int:writeData(ret, ffi.c.int:readData(a) + ffi.c.int:readData(b))
|
||||
c.int:writeData(ret, c.int:readData(a) + c.int:readData(b))
|
||||
end)
|
||||
|
||||
local closure_test_info = ffi.c.fn({ callback_info }, ffi.c.int)
|
||||
local closure_test_info = c.fn({ callback_info }, c.int)
|
||||
|
||||
local closure_test_callable = closure_test_info:callable(lib:find("closure"))
|
||||
|
||||
local result_box = ffi.box(ffi.c.int.size)
|
||||
local result_box = ffi.box(c.int.size)
|
||||
closure_test_callable(result_box, callback_closure:ref())
|
||||
local result = ffi.c.int:readData(result_box)
|
||||
local result = c.int:readData(result_box)
|
||||
assert(result == 72, `test_closure failed. result expected 20000, got {result}`)
|
||||
end
|
||||
|
||||
test_closure()
|
||||
|
||||
local function test_hello_world()
|
||||
local callback_info = ffi.c.fn({}, ffi.c.void)
|
||||
local callback_info = c.fn({}, c.void)
|
||||
local callback_closure = callback_info:closure(function()
|
||||
print("Hello world in lua closure!")
|
||||
end)
|
||||
|
||||
local closure_test_info = ffi.c.fn({ callback_info }, ffi.c.void)
|
||||
local closure_test_info = c.fn({ callback_info }, c.void)
|
||||
|
||||
local closure_test_callable = closure_test_info:callable(lib:find("hello_world"))
|
||||
|
||||
|
|
12
tests/ffi/utility/deno.ts
Normal file
12
tests/ffi/utility/deno.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export let libSuffix = "";
|
||||
switch (Deno.build.os) {
|
||||
case "windows":
|
||||
libSuffix = "dll";
|
||||
break;
|
||||
case "darwin":
|
||||
libSuffix = "dylib";
|
||||
break;
|
||||
case "linux":
|
||||
libSuffix = "so";
|
||||
break;
|
||||
}
|
25
tests/ffi/utility/proc_clock/deno.ts
Normal file
25
tests/ffi/utility/proc_clock/deno.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { libSuffix } from "../deno.ts";
|
||||
|
||||
const library_file = "./tests/ffi/utility/proc_clock/lib."+libSuffix;
|
||||
let library = Deno.dlopen(library_file, {
|
||||
sizeof_clock: {
|
||||
parameters: [],
|
||||
result: "i32",
|
||||
},
|
||||
});
|
||||
const sizeof_clock = library.symbols.sizeof_clock();
|
||||
const type_clock_t = "u" + (sizeof_clock * 8);
|
||||
library.close();
|
||||
library = Deno.dlopen(library_file, {
|
||||
get_clock: {
|
||||
parameters: [],
|
||||
result: type_clock_t,
|
||||
},
|
||||
get_offset: {
|
||||
parameters: [type_clock_t, type_clock_t],
|
||||
result: "f64",
|
||||
},
|
||||
});
|
||||
|
||||
export const get_clock = library.symbols.get_clock;
|
||||
export const get_offset = library.symbols.get_offset;
|
37
tests/ffi/utility/proc_clock/init.luau
Normal file
37
tests/ffi/utility/proc_clock/init.luau
Normal file
|
@ -0,0 +1,37 @@
|
|||
local ffi = require("@lune/ffi")
|
||||
local c = ffi.c
|
||||
|
||||
local proc_clock = {}
|
||||
|
||||
local libdir = "./tests/ffi/utility/proc_clock"
|
||||
local compile = require("../compile")
|
||||
compile(`{libdir}/lib.c`, `{libdir}/lib.so`)
|
||||
local lib = ffi.open(`{libdir}/lib.so`)
|
||||
|
||||
-- sizeof_clock
|
||||
local sizeof_clock = c.fn({}, c.int):callable(lib:find("sizeof_clock"))
|
||||
function proc_clock.sizeof_clock()
|
||||
local result = ffi.box(c.int.size)
|
||||
sizeof_clock(result)
|
||||
return c.int:readData(result)
|
||||
end
|
||||
|
||||
-- get_clock
|
||||
local clock_t = ffi["u" .. (proc_clock.sizeof_clock() * 8)]
|
||||
assert(clock_t, "clock_t is unknown type")
|
||||
local get_clock = c.fn({}, clock_t):callable(lib:find("get_clock"))
|
||||
proc_clock.get_clock = get_clock
|
||||
|
||||
-- get_offset
|
||||
local get_offset = c.fn({ clock_t, clock_t }, ffi.f64):callable(lib:find("get_offset"))
|
||||
function proc_clock.get_offset(before, after)
|
||||
local result = ffi.box(ffi.f64.size)
|
||||
get_offset(result, before:ref(), after:ref())
|
||||
return ffi.f64:readData(result)
|
||||
end
|
||||
|
||||
function proc_clock.new_box()
|
||||
return ffi.box(clock_t.size), ffi.box(clock_t.size)
|
||||
end
|
||||
|
||||
return proc_clock
|
12
tests/ffi/utility/proc_clock/lib.c
Normal file
12
tests/ffi/utility/proc_clock/lib.c
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include <time.h>
|
||||
clock_t get_clock() {
|
||||
return clock();
|
||||
}
|
||||
|
||||
int sizeof_clock() {
|
||||
return sizeof(clock_t);
|
||||
}
|
||||
|
||||
double get_offset(clock_t before, clock_t after) {
|
||||
return (double)(after - before) / CLOCKS_PER_SEC;
|
||||
}
|
|
@ -119,6 +119,15 @@ export type CallableData = (ret: (RefData|BoxData)?, ...RefData)->() & {
|
|||
A lua function wrapper for function pointer
|
||||
]=]
|
||||
export type ClosureData = {
|
||||
--[=[
|
||||
@within ClosureData
|
||||
@tag Method
|
||||
@method ref
|
||||
|
||||
Create a reference of the closure. usually can be used for passing function pointer as argument
|
||||
|
||||
@return A reference of the closure
|
||||
]=]
|
||||
ref: (self: ClosureData)->RefData,
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue