From eb772cfcf92af0ab291b053f5059b69e8a1d8f20 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 12 Mar 2022 16:25:16 +0200 Subject: [PATCH 1/7] checker,cgen: check sort_with_compare_context parameters too --- vlib/v/checker/fn.v | 49 +++++++++++-------- .../tests/array_sort_with_compare_err.out | 6 +-- .../array_sort_with_compare_ref_elem_err.out | 2 +- vlib/v/gen/c/fn.v | 2 +- vlib/v/gen/js/fn.v | 2 +- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 355be70ecf..6b9b524812 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1297,26 +1297,12 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { c.fail_if_unreadable(arg.expr, got_arg_typ, 'argument') } } - if left_sym.kind == .array && method_name == 'sort_with_compare' { - array_info := left_sym.info as ast.Array - elem_typ := array_info.elem_type - arg_sym := c.table.sym(arg.typ) - if arg_sym.kind == .function { - func_info := arg_sym.info as ast.FnType - if func_info.func.params.len == 2 { - if func_info.func.params[0].typ.nr_muls() != elem_typ.nr_muls() + 1 { - arg_typ_str := c.table.type_to_str(func_info.func.params[0].typ) - expected_typ_str := c.table.type_to_str(elem_typ.ref()) - c.error('sort_with_compare callback function parameter `${func_info.func.params[0].name}` with type `$arg_typ_str` should be `$expected_typ_str`', - func_info.func.params[0].type_pos) - } - if func_info.func.params[1].typ.nr_muls() != elem_typ.nr_muls() + 1 { - arg_typ_str := c.table.type_to_str(func_info.func.params[1].typ) - expected_typ_str := c.table.type_to_str(elem_typ.ref()) - c.error('sort_with_compare callback function parameter `${func_info.func.params[1].name}` with type `$arg_typ_str` should be `$expected_typ_str`', - func_info.func.params[1].type_pos) - } - } + if left_sym.kind == .array { + if method_name == 'sort_with_compare' { + c.check_sort_cb_args(2, 'sort_with_compare', left_sym, arg) + } + if method_name == 'sort_with_compare_context' { + c.check_sort_cb_args(3, 'sort_with_compare_context', left_sym, arg) } } // Handle expected interface @@ -1844,3 +1830,26 @@ fn scope_register_a_b(mut s ast.Scope, pos token.Pos, typ ast.Type) { is_used: true }) } + +fn (mut c Checker) check_sort_cb_args(howmany int, fname string, left_sym &ast.TypeSymbol, arg &ast.CallArg) { + array_info := left_sym.info as ast.Array + elem_typ := array_info.elem_type + arg_sym := c.table.sym(arg.typ) + if arg_sym.kind != .function { + return + } + func_info := arg_sym.info as ast.FnType + if func_info.func.params.len != howmany { + c.error('$fname callback should have exactly $howmany parameters', arg.expr.pos()) + return + } + desired_et_muls := elem_typ.nr_muls() + 1 + expected_typ_str := c.table.type_to_str(elem_typ.ref()) + for k in 0 .. 2 { + if func_info.func.params[k].typ.nr_muls() != desired_et_muls { + arg_typ_str := c.table.type_to_str(func_info.func.params[k].typ) + c.error('$fname callback function parameter `${func_info.func.params[k].name}` with type `$arg_typ_str` should be `$expected_typ_str`', + func_info.func.params[k].type_pos) + } + } +} diff --git a/vlib/v/checker/tests/array_sort_with_compare_err.out b/vlib/v/checker/tests/array_sort_with_compare_err.out index 47b67389cb..ab7913431c 100644 --- a/vlib/v/checker/tests/array_sort_with_compare_err.out +++ b/vlib/v/checker/tests/array_sort_with_compare_err.out @@ -1,13 +1,13 @@ vlib/v/checker/tests/array_sort_with_compare_err.vv:11:24: error: sort_with_compare callback function parameter `a` with type `string` should be `&string` 9 | } - 10 | + 10 | 11 | fn sort_by_file_base(a string, b string) int { | ~~~~~~ 12 | return int(a > b) 13 | } vlib/v/checker/tests/array_sort_with_compare_err.vv:4:26: error: cannot use `fn (string, string) int` as `fn (voidptr, voidptr) int` in argument 1 to `[]string.sort_with_compare` 2 | mut names := ['aaa', 'bbb', 'ccc'] - 3 | + 3 | 4 | names.sort_with_compare(sort_by_file_base) | ~~~~~~~~~~~~~~~~~ 5 | println(names) @@ -15,7 +15,7 @@ vlib/v/checker/tests/array_sort_with_compare_err.vv:4:26: error: cannot use `fn Details: ``'s expected fn argument: `` is a pointer, but the passed fn argument: `a` is NOT a pointer vlib/v/checker/tests/array_sort_with_compare_err.vv:7:26: error: cannot use `int literal` as `fn (voidptr, voidptr) int` in argument 1 to `[]string.sort_with_compare` 5 | println(names) - 6 | + 6 | 7 | names.sort_with_compare(22) | ~~ 8 | println(names) diff --git a/vlib/v/checker/tests/array_sort_with_compare_ref_elem_err.out b/vlib/v/checker/tests/array_sort_with_compare_ref_elem_err.out index 32915e9ba3..16d5d96e81 100644 --- a/vlib/v/checker/tests/array_sort_with_compare_ref_elem_err.out +++ b/vlib/v/checker/tests/array_sort_with_compare_ref_elem_err.out @@ -1,6 +1,6 @@ vlib/v/checker/tests/array_sort_with_compare_ref_elem_err.vv:21:23: error: sort_with_compare callback function parameter `a` with type `&Cell` should be `&&Cell` 19 | } - 20 | + 20 | 21 | fn sort_cells_by_yx(a &Cell, b &Cell) int { | ^ 22 | if a.pos.y == b.pos.y { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 15ddbc5b97..a3414564c6 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -952,7 +952,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { receiver_type_name = 'map' } if final_left_sym.kind == .array - && node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + && node.name in ['repeat', 'sort_with_compare', 'sort_with_compare_context', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { // `array_Xyz_clone` => `array_clone` receiver_type_name = 'array' diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index d583111314..80660d4584 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -318,7 +318,7 @@ fn (mut g JsGen) method_call(node ast.CallExpr) { } } if final_left_sym.kind == .array - && node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + && node.name in ['repeat', 'sort_with_compare', 'sort_with_compare_context', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { // `array_Xyz_clone` => `array_clone` receiver_type_name = 'array' From 17226830c8c54817c3baddeaedfcee1eee194e77 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 12 Mar 2022 18:12:57 +0200 Subject: [PATCH 2/7] builtin: implement array.sort_with_compare_context --- vlib/builtin/array.v | 12 ++++++- vlib/builtin/cfns_qsort_default.c.v | 4 +++ vlib/builtin/cfns_qsort_linux.c.v | 3 ++ vlib/builtin/cfns_qsort_windows.c.v | 3 ++ vlib/builtin/cfns_wrapper.c.v | 34 +++++++++++++++++++ vlib/builtin/sort_with_compare_context_test.v | 29 ++++++++++++++++ vlib/v/gen/c/cheaders.v | 4 +++ 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 vlib/builtin/cfns_qsort_default.c.v create mode 100644 vlib/builtin/cfns_qsort_linux.c.v create mode 100644 vlib/builtin/cfns_qsort_windows.c.v create mode 100644 vlib/builtin/sort_with_compare_context_test.v diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 2d7b712fb9..23067f6601 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -756,12 +756,22 @@ pub fn (mut a array) sort(callback fn (voidptr, voidptr) int) // ``` pub fn (mut a array) sort_with_compare(callback fn (voidptr, voidptr) int) { $if freestanding { - panic('sort does not work with -freestanding') + panic('sort_with_compare does not work with -freestanding') } $else { unsafe { vqsort(a.data, usize(a.len), usize(a.element_size), callback) } } } +pub fn (mut a array) sort_with_compare_context(callback fn (voidptr, voidptr, voidptr) int, context voidptr) { + $if freestanding { + panic('sort_with_compare_context does not work with -freestanding') + } $else { + unsafe { + vqsort_context(a.data, usize(a.len), usize(a.element_size), callback, context) + } + } +} + // contains determines whether an array includes a certain value among its elements // It will return `true` if the array contains an element with this value. // It is similar to `.any` but does not take an `it` expression. diff --git a/vlib/builtin/cfns_qsort_default.c.v b/vlib/builtin/cfns_qsort_default.c.v new file mode 100644 index 0000000000..2e68754e7a --- /dev/null +++ b/vlib/builtin/cfns_qsort_default.c.v @@ -0,0 +1,4 @@ +module builtin + +fn C.qsort_r(base voidptr, items usize, item_size usize, context voidptr, cb C.qsort_r_bsd_callback_func_context) +fn C.qsort_s(base voidptr, items usize, item_size usize, cb C.qsort_s_iso_callback_func_context, context voidptr) diff --git a/vlib/builtin/cfns_qsort_linux.c.v b/vlib/builtin/cfns_qsort_linux.c.v new file mode 100644 index 0000000000..5e12c2ca39 --- /dev/null +++ b/vlib/builtin/cfns_qsort_linux.c.v @@ -0,0 +1,3 @@ +module builtin + +fn C.qsort_r(base voidptr, items usize, item_size usize, cb C.qsort_r_gnu_callback_func_context, context voidptr) diff --git a/vlib/builtin/cfns_qsort_windows.c.v b/vlib/builtin/cfns_qsort_windows.c.v new file mode 100644 index 0000000000..cc4aa21cb2 --- /dev/null +++ b/vlib/builtin/cfns_qsort_windows.c.v @@ -0,0 +1,3 @@ +module builtin + +fn C.qsort_s(base voidptr, items usize, item_size usize, cb C.qsort_s_windows_callback_func_context, context voidptr) diff --git a/vlib/builtin/cfns_wrapper.c.v b/vlib/builtin/cfns_wrapper.c.v index 30d45b55fb..5038a3b9ec 100644 --- a/vlib/builtin/cfns_wrapper.c.v +++ b/vlib/builtin/cfns_wrapper.c.v @@ -66,7 +66,41 @@ pub fn vmemset(s voidptr, c int, n int) voidptr { type FnSortCB = fn (const_a voidptr, const_b voidptr) int +type FnSortContextCB = fn (const_a voidptr, const_b voidptr, context voidptr) int + [inline; unsafe] fn vqsort(base voidptr, nmemb usize, size usize, sort_cb FnSortCB) { C.qsort(base, nmemb, size, voidptr(sort_cb)) } + +struct VIndirectQSortContext { +mut: + real_context voidptr + real_sort_cb FnSortContextCB +} + +[inline; unsafe] +fn vqsort_context(base voidptr, nmemb usize, size usize, sort_cb FnSortContextCB, context voidptr) { + // See https://stackoverflow.com/questions/39560773/different-declarations-of-qsort-r-on-mac-and-linux + // ... and https://xkcd.com/927/ :-| + $if linux { + C.qsort_r(base, nmemb, size, voidptr(sort_cb), context) + } $else { + ic := VIndirectQSortContext{ + real_context: context + real_sort_cb: sort_cb + } + $if windows { + cb := fn (context &VIndirectQSortContext, const_a voidptr, const_b voidptr) int { + return context.real_sort_cb(const_a, const_b, context.real_context) + } + C.qsort_s(base, nmemb, size, voidptr(cb), &ic) + } $else { + // macos, BSDs, probably other unixes too: + cb := fn (context &VIndirectQSortContext, const_a voidptr, const_b voidptr) int { + return context.real_sort_cb(const_a, const_b, context.real_context) + } + C.qsort_r(base, nmemb, size, &ic, voidptr(cb)) + } + } +} diff --git a/vlib/builtin/sort_with_compare_context_test.v b/vlib/builtin/sort_with_compare_context_test.v new file mode 100644 index 0000000000..3cce17c886 --- /dev/null +++ b/vlib/builtin/sort_with_compare_context_test.v @@ -0,0 +1,29 @@ +struct Context { +mut: + comparisons []string +} + +fn test_sort_with_compare() { + mut a := ['hi', '1', '5', '3'] + mut context := Context{} + a.sort_with_compare_context(fn (a &string, b &string, mut context Context) int { + context.comparisons << 'a: ${*a} | b: ${*b}' + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 + }, context) + dump(a) + dump(context) + assert a == ['1', '3', '5', 'hi'] + assert context.comparisons == [ + 'a: hi | b: 1', + 'a: 5 | b: 3', + 'a: 1 | b: 3', + 'a: hi | b: 3', + 'a: hi | b: 5', + ] +} diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index c7117e1c1e..1786b563ed 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -478,6 +478,10 @@ const c_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_m r' // c_headers typedef int (*qsort_callback_func)(const void*, const void*); +typedef int (*qsort_s_iso_callback_func_context)(const void*, const void*, void*); // ISO C11, *optional* Annex K +typedef int (*qsort_s_windows_callback_func_context)(void*, const void*, const void*); // Windows +typedef int (*qsort_r_gnu_callback_func_context)(const void*, const void*, void*); // Linux, GNU +typedef int (*qsort_r_bsd_callback_func_context)(void*, const void*, const void*); // MacOS, BSD #include // TODO remove all these includes, define all function signatures and types manually #include #include From cc8f40df8607aebee8ef177173d12deaaf95ddf7 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 13 Mar 2022 09:51:09 +0200 Subject: [PATCH 3/7] draft of the pure V version --- vlib/builtin/cfns_wrapper.c.v | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vlib/builtin/cfns_wrapper.c.v b/vlib/builtin/cfns_wrapper.c.v index 5038a3b9ec..32aff567ba 100644 --- a/vlib/builtin/cfns_wrapper.c.v +++ b/vlib/builtin/cfns_wrapper.c.v @@ -79,12 +79,16 @@ mut: real_sort_cb FnSortContextCB } +fn vqsort_context_pure_v(base voidptr, nmemb usize, size usize, sort_cb FnSortContextCB, context voidptr) { +} + [inline; unsafe] fn vqsort_context(base voidptr, nmemb usize, size usize, sort_cb FnSortContextCB, context voidptr) { // See https://stackoverflow.com/questions/39560773/different-declarations-of-qsort-r-on-mac-and-linux // ... and https://xkcd.com/927/ :-| $if linux { - C.qsort_r(base, nmemb, size, voidptr(sort_cb), context) + vqsort_context_pure_v(base, nmemb, size, sort_cb, context) + // C.qsort_r(base, nmemb, size, voidptr(sort_cb), context) } $else { ic := VIndirectQSortContext{ real_context: context From 4535b5edc2cbb4a45c2f86476e4c7cf962501814 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 27 Mar 2022 10:45:02 +0300 Subject: [PATCH 4/7] make the regression test more thorough; prepare for the pure V version --- vlib/builtin/cfns_wrapper.c.v | 6 +++ vlib/builtin/sort_with_compare_context_test.v | 52 +++++++++++++------ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/vlib/builtin/cfns_wrapper.c.v b/vlib/builtin/cfns_wrapper.c.v index 32aff567ba..f3f9c93eec 100644 --- a/vlib/builtin/cfns_wrapper.c.v +++ b/vlib/builtin/cfns_wrapper.c.v @@ -79,7 +79,13 @@ mut: real_sort_cb FnSortContextCB } + +// GLIBC: +// void qsort_r(void *base, size_t nmemb, size_t size, +// int (*compar)(const void *, const void *, void *), +// void *arg); fn vqsort_context_pure_v(base voidptr, nmemb usize, size usize, sort_cb FnSortContextCB, context voidptr) { + C.qsort_r(base, nmemb, size, voidptr(sort_cb), context) } [inline; unsafe] diff --git a/vlib/builtin/sort_with_compare_context_test.v b/vlib/builtin/sort_with_compare_context_test.v index 3cce17c886..a8ab6efb68 100644 --- a/vlib/builtin/sort_with_compare_context_test.v +++ b/vlib/builtin/sort_with_compare_context_test.v @@ -3,27 +3,49 @@ mut: comparisons []string } +fn (c Context) str() string { + mut res := []string{} + for x in c.comparisons { + res << x + } + return '\n' + res.join('\n') +} + +fn sort_cb(a &string, b &string, mut context Context) int { + context.comparisons << 'a: "${*a}" | b: "${*b}"' + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + fn test_sort_with_compare() { mut a := ['hi', '1', '5', '3'] mut context := Context{} - a.sort_with_compare_context(fn (a &string, b &string, mut context Context) int { - context.comparisons << 'a: ${*a} | b: ${*b}' - if a < b { - return -1 - } - if a > b { - return 1 - } - return 0 - }, context) + a.sort_with_compare_context(sort_cb, context) dump(a) dump(context) assert a == ['1', '3', '5', 'hi'] assert context.comparisons == [ - 'a: hi | b: 1', - 'a: 5 | b: 3', - 'a: 1 | b: 3', - 'a: hi | b: 3', - 'a: hi | b: 5', + 'a: "hi" | b: "1"', + 'a: "5" | b: "3"', + 'a: "1" | b: "3"', + 'a: "hi" | b: "3"', + 'a: "hi" | b: "5"', + ] + // + mut already_sorted_context := Context{} + a.sort_with_compare_context(sort_cb, already_sorted_context) + dump(a) + dump(already_sorted_context) + assert context != already_sorted_context + assert already_sorted_context.comparisons == [ + 'a: "1" | b: "3"', + 'a: "5" | b: "hi"', + 'a: "1" | b: "5"', + 'a: "3" | b: "5"', ] } From 7993ff295e35ba702d6f9020ff037a804ad48d4e Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Mon, 28 Mar 2022 23:27:05 +0300 Subject: [PATCH 5/7] add the qsort_r implementation from musl (reduced pathological performance degradations) --- thirdparty/musl_qsort/vqsort_r.c | 210 ++++++++++++++++++ thirdparty/musl_qsort/vqsort_r.h | 3 + vlib/builtin/cfns_qsort_default.c.v | 4 - vlib/builtin/cfns_qsort_linux.c.v | 3 - vlib/builtin/cfns_qsort_windows.c.v | 3 - vlib/builtin/cfns_wrapper.c.v | 44 +--- vlib/builtin/sort_with_compare_context_test.v | 14 +- vlib/v/gen/c/cheaders.v | 4 - 8 files changed, 226 insertions(+), 59 deletions(-) create mode 100644 thirdparty/musl_qsort/vqsort_r.c create mode 100644 thirdparty/musl_qsort/vqsort_r.h delete mode 100644 vlib/builtin/cfns_qsort_default.c.v delete mode 100644 vlib/builtin/cfns_qsort_linux.c.v delete mode 100644 vlib/builtin/cfns_qsort_windows.c.v diff --git a/thirdparty/musl_qsort/vqsort_r.c b/thirdparty/musl_qsort/vqsort_r.c new file mode 100644 index 0000000000..5f472f6d1d --- /dev/null +++ b/thirdparty/musl_qsort/vqsort_r.c @@ -0,0 +1,210 @@ + +#include +#include +#include + +static inline int a_ctz_32(uint32_t x) { + static const char debruijn32[32] = { 0, 1, 23, 2, 29, 24, 19, 3, 30, 27, 25, 11, 20, 8, 4, 13, 31, 22, 28, 18, 26, 10, 7, 12, 21, 17, 9, 6, 16, 5, 15, 14 }; + return debruijn32[(x&-x)*0x076be629 >> 27]; +} + +static inline int a_ctz_64(uint64_t x) { + static const char debruijn64[64] = { + 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 + }; + if (sizeof(long) < 8) { + uint32_t y = x; + if (!y) { + y = x>>32; + return 32 + a_ctz_32(y); + } + return a_ctz_32(y); + } + return debruijn64[(x&-x)*0x022fdd63cc95386dull >> 58]; +} + +static inline int a_ctz_l(unsigned long x) { + return (sizeof(long) < 8) ? a_ctz_32(x) : a_ctz_64(x); +} + +typedef int (*vqsort_r_cmpfun)(const void *, const void *, void *); + +static inline int pntz(size_t p[2]) { + int r = a_ctz_l(p[0] - 1); + if(r != 0 || (r = 8*sizeof(size_t) + a_ctz_l(p[1])) != 8*sizeof(size_t)) { + return r; + } + return 0; +} + +static void cycle(size_t width, unsigned char* ar[], int n) { + unsigned char tmp[256]; + size_t l; + int i; + + if(n < 2) { + return; + } + + ar[n] = tmp; + while(width) { + l = sizeof(tmp) < width ? sizeof(tmp) : width; + memcpy(ar[n], ar[0], l); + for(i = 0; i < n; i++) { + memcpy(ar[i], ar[i + 1], l); + ar[i] += l; + } + width -= l; + } +} + +/* shl() and shr() need n > 0 */ +static inline void shl(size_t p[2], int n) { + if(n >= 8 * sizeof(size_t)) { + n -= 8 * sizeof(size_t); + p[1] = p[0]; + p[0] = 0; + } + p[1] <<= n; + p[1] |= p[0] >> (sizeof(size_t) * 8 - n); + p[0] <<= n; +} + +static inline void shr(size_t p[2], int n) { + if(n >= 8 * sizeof(size_t)) { + n -= 8 * sizeof(size_t); + p[0] = p[1]; + p[1] = 0; + } + p[0] >>= n; + p[0] |= p[1] << (sizeof(size_t) * 8 - n); + p[1] >>= n; +} + +static void sift(unsigned char *head, size_t width, vqsort_r_cmpfun cmp, void *arg, int pshift, size_t lp[]) { + unsigned char *rt, *lf; + unsigned char *ar[14 * sizeof(size_t) + 1]; + int i = 1; + + ar[0] = head; + while(pshift > 1) { + rt = head - width; + lf = head - width - lp[pshift - 2]; + + if(cmp(ar[0], lf, arg) >= 0 && cmp(ar[0], rt, arg) >= 0) { + break; + } + if(cmp(lf, rt, arg) >= 0) { + ar[i++] = lf; + head = lf; + pshift -= 1; + } else { + ar[i++] = rt; + head = rt; + pshift -= 2; + } + } + cycle(width, ar, i); +} + +static void trinkle(unsigned char *head, size_t width, vqsort_r_cmpfun cmp, void *arg, size_t pp[2], int pshift, int trusty, size_t lp[]) { + unsigned char *stepson, *rt, *lf; + size_t p[2]; + unsigned char *ar[14 * sizeof(size_t) + 1]; + int i = 1; + int trail; + + p[0] = pp[0]; + p[1] = pp[1]; + + ar[0] = head; + while(p[0] != 1 || p[1] != 0) { + stepson = head - lp[pshift]; + if(cmp(stepson, ar[0], arg) <= 0) { + break; + } + if(!trusty && pshift > 1) { + rt = head - width; + lf = head - width - lp[pshift - 2]; + if(cmp(rt, stepson, arg) >= 0 || cmp(lf, stepson, arg) >= 0) { + break; + } + } + + ar[i++] = stepson; + head = stepson; + trail = pntz(p); + shr(p, trail); + pshift += trail; + trusty = 0; + } + if(!trusty) { + cycle(width, ar, i); + sift(head, width, cmp, arg, pshift, lp); + } +} + +void vqsort_r(void *base, size_t nel, size_t width, vqsort_r_cmpfun cmp, void *arg) { + size_t lp[12*sizeof(size_t)]; + size_t i, size = width * nel; + unsigned char *head, *high; + size_t p[2] = {1, 0}; + int pshift = 1; + int trail; + + if (!size) return; + + head = base; + high = head + size - width; + + /* Precompute Leonardo numbers, scaled by element width */ + for(lp[0]=lp[1]=width, i=2; (lp[i]=lp[i-2]+lp[i-1]+width) < size; i++); + + while(head < high) { + if((p[0] & 3) == 3) { + sift(head, width, cmp, arg, pshift, lp); + shr(p, 2); + pshift += 2; + } else { + if(lp[pshift - 1] >= high - head) { + trinkle(head, width, cmp, arg, p, pshift, 0, lp); + } else { + sift(head, width, cmp, arg, pshift, lp); + } + + if(pshift == 1) { + shl(p, 1); + pshift = 0; + } else { + shl(p, pshift - 1); + pshift = 1; + } + } + + p[0] |= 1; + head += width; + } + + trinkle(head, width, cmp, arg, p, pshift, 0, lp); + + while(pshift != 1 || p[0] != 1 || p[1] != 0) { + if(pshift <= 1) { + trail = pntz(p); + shr(p, trail); + pshift += trail; + } else { + shl(p, 2); + pshift -= 2; + p[0] ^= 7; + shr(p, 1); + trinkle(head - lp[pshift] - width, width, cmp, arg, p, pshift + 1, 1, lp); + shl(p, 1); + p[0] |= 1; + trinkle(head - width, width, cmp, arg, p, pshift, 1, lp); + } + head -= width; + } +} diff --git a/thirdparty/musl_qsort/vqsort_r.h b/thirdparty/musl_qsort/vqsort_r.h new file mode 100644 index 0000000000..b2da61eef4 --- /dev/null +++ b/thirdparty/musl_qsort/vqsort_r.h @@ -0,0 +1,3 @@ + +typedef int (*vqsort_r_cmpfun)(const void *, const void *, void *); +void vqsort_r(void *base, size_t nel, size_t width, vqsort_r_cmpfun cmp, void *arg); diff --git a/vlib/builtin/cfns_qsort_default.c.v b/vlib/builtin/cfns_qsort_default.c.v deleted file mode 100644 index 2e68754e7a..0000000000 --- a/vlib/builtin/cfns_qsort_default.c.v +++ /dev/null @@ -1,4 +0,0 @@ -module builtin - -fn C.qsort_r(base voidptr, items usize, item_size usize, context voidptr, cb C.qsort_r_bsd_callback_func_context) -fn C.qsort_s(base voidptr, items usize, item_size usize, cb C.qsort_s_iso_callback_func_context, context voidptr) diff --git a/vlib/builtin/cfns_qsort_linux.c.v b/vlib/builtin/cfns_qsort_linux.c.v deleted file mode 100644 index 5e12c2ca39..0000000000 --- a/vlib/builtin/cfns_qsort_linux.c.v +++ /dev/null @@ -1,3 +0,0 @@ -module builtin - -fn C.qsort_r(base voidptr, items usize, item_size usize, cb C.qsort_r_gnu_callback_func_context, context voidptr) diff --git a/vlib/builtin/cfns_qsort_windows.c.v b/vlib/builtin/cfns_qsort_windows.c.v deleted file mode 100644 index cc4aa21cb2..0000000000 --- a/vlib/builtin/cfns_qsort_windows.c.v +++ /dev/null @@ -1,3 +0,0 @@ -module builtin - -fn C.qsort_s(base voidptr, items usize, item_size usize, cb C.qsort_s_windows_callback_func_context, context voidptr) diff --git a/vlib/builtin/cfns_wrapper.c.v b/vlib/builtin/cfns_wrapper.c.v index f3f9c93eec..b8d283f47f 100644 --- a/vlib/builtin/cfns_wrapper.c.v +++ b/vlib/builtin/cfns_wrapper.c.v @@ -1,5 +1,8 @@ module builtin +#include "@VEXEROOT/thirdparty/musl_qsort/vqsort_r.h" +#flag @VEXEROOT/thirdparty/musl_qsort/vqsort_r.o + // vstrlen returns the V length of the C string `s` (0 terminator is not counted). // The C string is expected to be a &byte pointer. [inline; unsafe] @@ -65,52 +68,15 @@ pub fn vmemset(s voidptr, c int, n int) voidptr { } type FnSortCB = fn (const_a voidptr, const_b voidptr) int - type FnSortContextCB = fn (const_a voidptr, const_b voidptr, context voidptr) int +fn C.vqsort_r(base voidptr, nel usize, width usize, cb FnSortContextCB, context voidptr) [inline; unsafe] fn vqsort(base voidptr, nmemb usize, size usize, sort_cb FnSortCB) { C.qsort(base, nmemb, size, voidptr(sort_cb)) } -struct VIndirectQSortContext { -mut: - real_context voidptr - real_sort_cb FnSortContextCB -} - - -// GLIBC: -// void qsort_r(void *base, size_t nmemb, size_t size, -// int (*compar)(const void *, const void *, void *), -// void *arg); -fn vqsort_context_pure_v(base voidptr, nmemb usize, size usize, sort_cb FnSortContextCB, context voidptr) { - C.qsort_r(base, nmemb, size, voidptr(sort_cb), context) -} - [inline; unsafe] fn vqsort_context(base voidptr, nmemb usize, size usize, sort_cb FnSortContextCB, context voidptr) { - // See https://stackoverflow.com/questions/39560773/different-declarations-of-qsort-r-on-mac-and-linux - // ... and https://xkcd.com/927/ :-| - $if linux { - vqsort_context_pure_v(base, nmemb, size, sort_cb, context) - // C.qsort_r(base, nmemb, size, voidptr(sort_cb), context) - } $else { - ic := VIndirectQSortContext{ - real_context: context - real_sort_cb: sort_cb - } - $if windows { - cb := fn (context &VIndirectQSortContext, const_a voidptr, const_b voidptr) int { - return context.real_sort_cb(const_a, const_b, context.real_context) - } - C.qsort_s(base, nmemb, size, voidptr(cb), &ic) - } $else { - // macos, BSDs, probably other unixes too: - cb := fn (context &VIndirectQSortContext, const_a voidptr, const_b voidptr) int { - return context.real_sort_cb(const_a, const_b, context.real_context) - } - C.qsort_r(base, nmemb, size, &ic, voidptr(cb)) - } - } + C.vqsort_r(base, nmemb, size, voidptr(sort_cb), context) } diff --git a/vlib/builtin/sort_with_compare_context_test.v b/vlib/builtin/sort_with_compare_context_test.v index a8ab6efb68..aede8470c5 100644 --- a/vlib/builtin/sort_with_compare_context_test.v +++ b/vlib/builtin/sort_with_compare_context_test.v @@ -29,12 +29,14 @@ fn test_sort_with_compare() { dump(a) dump(context) assert a == ['1', '3', '5', 'hi'] + assert context.comparisons == [ + 'a: "5" | b: "hi"', 'a: "hi" | b: "1"', - 'a: "5" | b: "3"', - 'a: "1" | b: "3"', 'a: "hi" | b: "3"', - 'a: "hi" | b: "5"', + 'a: "3" | b: "5"', + 'a: "5" | b: "1"', + 'a: "3" | b: "1"' ] // mut already_sorted_context := Context{} @@ -43,9 +45,9 @@ fn test_sort_with_compare() { dump(already_sorted_context) assert context != already_sorted_context assert already_sorted_context.comparisons == [ - 'a: "1" | b: "3"', + 'a: "5" | b: "1"', + 'a: "5" | b: "3"', 'a: "5" | b: "hi"', - 'a: "1" | b: "5"', - 'a: "3" | b: "5"', + 'a: "1" | b: "3"' ] } diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index 1786b563ed..c7117e1c1e 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -478,10 +478,6 @@ const c_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_m r' // c_headers typedef int (*qsort_callback_func)(const void*, const void*); -typedef int (*qsort_s_iso_callback_func_context)(const void*, const void*, void*); // ISO C11, *optional* Annex K -typedef int (*qsort_s_windows_callback_func_context)(void*, const void*, const void*); // Windows -typedef int (*qsort_r_gnu_callback_func_context)(const void*, const void*, void*); // Linux, GNU -typedef int (*qsort_r_bsd_callback_func_context)(void*, const void*, const void*); // MacOS, BSD #include // TODO remove all these includes, define all function signatures and types manually #include #include From ede2b798f62ea8f8f66a41a9242b025e47657c4c Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Mon, 28 Mar 2022 23:32:58 +0300 Subject: [PATCH 6/7] vfmt cfns_wrapper.c.v and sort_with_compare_context_test.v --- vlib/builtin/cfns_wrapper.c.v | 2 ++ vlib/builtin/sort_with_compare_context_test.v | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vlib/builtin/cfns_wrapper.c.v b/vlib/builtin/cfns_wrapper.c.v index b8d283f47f..7ab5793bd7 100644 --- a/vlib/builtin/cfns_wrapper.c.v +++ b/vlib/builtin/cfns_wrapper.c.v @@ -68,7 +68,9 @@ pub fn vmemset(s voidptr, c int, n int) voidptr { } type FnSortCB = fn (const_a voidptr, const_b voidptr) int + type FnSortContextCB = fn (const_a voidptr, const_b voidptr, context voidptr) int + fn C.vqsort_r(base voidptr, nel usize, width usize, cb FnSortContextCB, context voidptr) [inline; unsafe] diff --git a/vlib/builtin/sort_with_compare_context_test.v b/vlib/builtin/sort_with_compare_context_test.v index aede8470c5..76d40b5e81 100644 --- a/vlib/builtin/sort_with_compare_context_test.v +++ b/vlib/builtin/sort_with_compare_context_test.v @@ -29,14 +29,14 @@ fn test_sort_with_compare() { dump(a) dump(context) assert a == ['1', '3', '5', 'hi'] - + assert context.comparisons == [ 'a: "5" | b: "hi"', 'a: "hi" | b: "1"', 'a: "hi" | b: "3"', 'a: "3" | b: "5"', 'a: "5" | b: "1"', - 'a: "3" | b: "1"' + 'a: "3" | b: "1"', ] // mut already_sorted_context := Context{} @@ -48,6 +48,6 @@ fn test_sort_with_compare() { 'a: "5" | b: "1"', 'a: "5" | b: "3"', 'a: "5" | b: "hi"', - 'a: "1" | b: "3"' + 'a: "1" | b: "3"', ] } From 4ffcf031fa0825c797f006aa480e50f074d8734a Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Tue, 29 Mar 2022 00:05:15 +0300 Subject: [PATCH 7/7] add a small sort comparison tool --- cmd/tools/bench/sorting.results.txt | 139 ++++++++++++++++++++++++++++ cmd/tools/bench/sorting.v | 79 ++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 cmd/tools/bench/sorting.results.txt create mode 100644 cmd/tools/bench/sorting.v diff --git a/cmd/tools/bench/sorting.results.txt b/cmd/tools/bench/sorting.results.txt new file mode 100644 index 0000000000..549bf585cc --- /dev/null +++ b/cmd/tools/bench/sorting.results.txt @@ -0,0 +1,139 @@ +# ./v -prod -cc clang-12 cmd/tools/bench/sorting.v +# for seqkind in increasing decreasing random; do +# for how in qsort aswcc; do +# for x in 10_000_000 1000_000 100_000 10_000 1000 100 10 ; do +# ./cmd/tools/bench/sorting $how $x $seqkind; +# done; +# done; +# echo --------------------------------; +# done > cmd/tools/bench/sorting.results.txt + +> a.len: 10000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000] +> a.len: 10000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000] +SPENT 461.034 ms in qsort 10000000 increasing +> a.len: 1000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [999994, 999995, 999996, 999997, 999998, 999999, 1000000] +> a.len: 1000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [999994, 999995, 999996, 999997, 999998, 999999, 1000000] +SPENT 40.616 ms in qsort 1000000 increasing +> a.len: 100000 | a: [1, 2, 3, 4, 5, 6, 7] ... [99994, 99995, 99996, 99997, 99998, 99999, 100000] +> a.len: 100000 | a: [1, 2, 3, 4, 5, 6, 7] ... [99994, 99995, 99996, 99997, 99998, 99999, 100000] +SPENT 4.415 ms in qsort 100000 increasing +> a.len: 10000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9994, 9995, 9996, 9997, 9998, 9999, 10000] +> a.len: 10000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9994, 9995, 9996, 9997, 9998, 9999, 10000] +SPENT 0.304 ms in qsort 10000 increasing +> a.len: 1000 | a: [1, 2, 3, 4, 5, 6, 7] ... [994, 995, 996, 997, 998, 999, 1000] +> a.len: 1000 | a: [1, 2, 3, 4, 5, 6, 7] ... [994, 995, 996, 997, 998, 999, 1000] +SPENT 0.042 ms in qsort 1000 increasing +> a.len: 100 | a: [1, 2, 3, 4, 5, 6, 7] ... [94, 95, 96, 97, 98, 99, 100] +> a.len: 100 | a: [1, 2, 3, 4, 5, 6, 7] ... [94, 95, 96, 97, 98, 99, 100] +SPENT 0.009 ms in qsort 100 increasing +> a.len: 10 | a: [1, 2, 3, 4, 5, 6, 7] ... [4, 5, 6, 7, 8, 9, 10] +> a.len: 10 | a: [1, 2, 3, 4, 5, 6, 7] ... [4, 5, 6, 7, 8, 9, 10] +SPENT 0.005 ms in qsort 10 increasing +> a.len: 10000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000] +> a.len: 10000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000] +SPENT 109.615 ms in aswcc 10000000 increasing +> a.len: 1000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [999994, 999995, 999996, 999997, 999998, 999999, 1000000] +> a.len: 1000000 | a: [1, 2, 3, 4, 5, 6, 7] ... [999994, 999995, 999996, 999997, 999998, 999999, 1000000] +SPENT 10.520 ms in aswcc 1000000 increasing +> a.len: 100000 | a: [1, 2, 3, 4, 5, 6, 7] ... [99994, 99995, 99996, 99997, 99998, 99999, 100000] +> a.len: 100000 | a: [1, 2, 3, 4, 5, 6, 7] ... [99994, 99995, 99996, 99997, 99998, 99999, 100000] +SPENT 1.038 ms in aswcc 100000 increasing +> a.len: 10000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9994, 9995, 9996, 9997, 9998, 9999, 10000] +> a.len: 10000 | a: [1, 2, 3, 4, 5, 6, 7] ... [9994, 9995, 9996, 9997, 9998, 9999, 10000] +SPENT 0.106 ms in aswcc 10000 increasing +> a.len: 1000 | a: [1, 2, 3, 4, 5, 6, 7] ... [994, 995, 996, 997, 998, 999, 1000] +> a.len: 1000 | a: [1, 2, 3, 4, 5, 6, 7] ... [994, 995, 996, 997, 998, 999, 1000] +SPENT 0.016 ms in aswcc 1000 increasing +> a.len: 100 | a: [1, 2, 3, 4, 5, 6, 7] ... [94, 95, 96, 97, 98, 99, 100] +> a.len: 100 | a: [1, 2, 3, 4, 5, 6, 7] ... [94, 95, 96, 97, 98, 99, 100] +SPENT 0.006 ms in aswcc 100 increasing +> a.len: 10 | a: [1, 2, 3, 4, 5, 6, 7] ... [4, 5, 6, 7, 8, 9, 10] +> a.len: 10 | a: [1, 2, 3, 4, 5, 6, 7] ... [4, 5, 6, 7, 8, 9, 10] +SPENT 0.005 ms in aswcc 10 increasing +-------------------------------- +> a.len: 9999999 | a: [9999999, 9999998, 9999997, 9999996, 9999995, 9999994, 9999993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 9999999 | a: [1, 2, 3, 4, 5, 6, 7] ... [9999993, 9999994, 9999995, 9999996, 9999997, 9999998, 9999999] +SPENT 452.092 ms in qsort 10000000 decreasing +> a.len: 999999 | a: [999999, 999998, 999997, 999996, 999995, 999994, 999993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 999999 | a: [1, 2, 3, 4, 5, 6, 7] ... [999993, 999994, 999995, 999996, 999997, 999998, 999999] +SPENT 39.840 ms in qsort 1000000 decreasing +> a.len: 99999 | a: [99999, 99998, 99997, 99996, 99995, 99994, 99993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 99999 | a: [1, 2, 3, 4, 5, 6, 7] ... [99993, 99994, 99995, 99996, 99997, 99998, 99999] +SPENT 3.609 ms in qsort 100000 decreasing +> a.len: 9999 | a: [9999, 9998, 9997, 9996, 9995, 9994, 9993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 9999 | a: [1, 2, 3, 4, 5, 6, 7] ... [9993, 9994, 9995, 9996, 9997, 9998, 9999] +SPENT 0.430 ms in qsort 10000 decreasing +> a.len: 999 | a: [999, 998, 997, 996, 995, 994, 993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 999 | a: [1, 2, 3, 4, 5, 6, 7] ... [993, 994, 995, 996, 997, 998, 999] +SPENT 0.058 ms in qsort 1000 decreasing +> a.len: 99 | a: [99, 98, 97, 96, 95, 94, 93] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 99 | a: [1, 2, 3, 4, 5, 6, 7] ... [93, 94, 95, 96, 97, 98, 99] +SPENT 0.013 ms in qsort 100 decreasing +> a.len: 9 | a: [9, 8, 7, 6, 5, 4, 3] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 9 | a: [1, 2, 3, 4, 5, 6, 7] ... [3, 4, 5, 6, 7, 8, 9] +SPENT 0.006 ms in qsort 10 decreasing +> a.len: 9999999 | a: [9999999, 9999998, 9999997, 9999996, 9999995, 9999994, 9999993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 9999999 | a: [1, 2, 3, 4, 5, 6, 7] ... [9999993, 9999994, 9999995, 9999996, 9999997, 9999998, 9999999] +SPENT 2313.316 ms in aswcc 10000000 decreasing +> a.len: 999999 | a: [999999, 999998, 999997, 999996, 999995, 999994, 999993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 999999 | a: [1, 2, 3, 4, 5, 6, 7] ... [999993, 999994, 999995, 999996, 999997, 999998, 999999] +SPENT 203.740 ms in aswcc 1000000 decreasing +> a.len: 99999 | a: [99999, 99998, 99997, 99996, 99995, 99994, 99993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 99999 | a: [1, 2, 3, 4, 5, 6, 7] ... [99993, 99994, 99995, 99996, 99997, 99998, 99999] +SPENT 17.406 ms in aswcc 100000 decreasing +> a.len: 9999 | a: [9999, 9998, 9997, 9996, 9995, 9994, 9993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 9999 | a: [1, 2, 3, 4, 5, 6, 7] ... [9993, 9994, 9995, 9996, 9997, 9998, 9999] +SPENT 1.467 ms in aswcc 10000 decreasing +> a.len: 999 | a: [999, 998, 997, 996, 995, 994, 993] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 999 | a: [1, 2, 3, 4, 5, 6, 7] ... [993, 994, 995, 996, 997, 998, 999] +SPENT 0.137 ms in aswcc 1000 decreasing +> a.len: 99 | a: [99, 98, 97, 96, 95, 94, 93] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 99 | a: [1, 2, 3, 4, 5, 6, 7] ... [93, 94, 95, 96, 97, 98, 99] +SPENT 0.015 ms in aswcc 100 decreasing +> a.len: 9 | a: [9, 8, 7, 6, 5, 4, 3] ... [7, 6, 5, 4, 3, 2, 1] +> a.len: 9 | a: [1, 2, 3, 4, 5, 6, 7] ... [3, 4, 5, 6, 7, 8, 9] +SPENT 0.005 ms in aswcc 10 decreasing +-------------------------------- +> a.len: 10000000 | a: [5860191, 5357534, 516370, 444218, 4782576, 3296560, 9597422] ... [9885313, 9298812, 1016850, 595010, 3994125, 6996906, 8189289] +> a.len: 10000000 | a: [1, 2, 3, 3, 4, 4, 5] ... [9999994, 9999994, 9999995, 9999996, 9999997, 9999998, 9999998] +SPENT 1490.203 ms in qsort 10000000 random +> a.len: 1000000 | a: [564075, 516370, 444218, 588272, 209384, 864959, 195323] ... [363186, 584539, 475461, 794706, 23108, 232638, 590085] +> a.len: 1000000 | a: [1, 1, 1, 1, 1, 3, 3] ... [999990, 999990, 999991, 999993, 999995, 999997, 999998] +SPENT 128.973 ms in qsort 1000000 random +> a.len: 100000 | a: [93023, 39787, 90906, 63984, 21510, 44477, 62876] ... [61585, 45923, 26539, 20911, 51779, 34695, 29023] +> a.len: 100000 | a: [1, 1, 2, 3, 4, 5, 7] ... [99991, 99992, 99994, 99994, 99998, 99998, 99999] +SPENT 10.583 ms in qsort 100000 random +> a.len: 10000 | a: [7019, 733, 7844, 97, 1994, 2517, 8308] ... [1293, 4423, 8853, 6021, 9549, 4161, 3700] +> a.len: 10000 | a: [1, 1, 2, 2, 2, 3, 3] ... [9992, 9992, 9994, 9995, 9998, 9998, 9999] +SPENT 0.873 ms in qsort 10000 random +> a.len: 1000 | a: [863, 875, 274, 733, 794, 496, 494] ... [530, 615, 371, 561, 800, 501, 585] +> a.len: 1000 | a: [2, 3, 4, 4, 5, 6, 6] ... [994, 994, 994, 994, 996, 997, 998] +SPENT 0.079 ms in qsort 1000 random +> a.len: 100 | a: [95, 18, 58, 26, 6, 48, 67] ... [11, 80, 16, 60, 58, 71, 1] +> a.len: 100 | a: [1, 3, 4, 5, 5, 6, 6] ... [94, 95, 95, 97, 97, 98, 99] +SPENT 0.010 ms in qsort 100 random +> a.len: 10 | a: [4, 6, 3, 8, 1, 4, 7] ... [8, 1, 4, 7, 5, 8, 4] +> a.len: 10 | a: [1, 3, 4, 4, 4, 5, 6] ... [4, 4, 5, 6, 7, 8, 8] +SPENT 0.005 ms in qsort 10 random +> a.len: 10000000 | a: [5860191, 5357534, 516370, 444218, 4782576, 3296560, 9597422] ... [9885313, 9298812, 1016850, 595010, 3994125, 6996906, 8189289] +> a.len: 10000000 | a: [1, 2, 3, 3, 4, 4, 5] ... [9999994, 9999994, 9999995, 9999996, 9999997, 9999998, 9999998] +SPENT 6279.383 ms in aswcc 10000000 random +> a.len: 1000000 | a: [564075, 516370, 444218, 588272, 209384, 864959, 195323] ... [363186, 584539, 475461, 794706, 23108, 232638, 590085] +> a.len: 1000000 | a: [1, 1, 1, 1, 1, 3, 3] ... [999990, 999990, 999991, 999993, 999995, 999997, 999998] +SPENT 369.465 ms in aswcc 1000000 random +> a.len: 100000 | a: [93023, 39787, 90906, 63984, 21510, 44477, 62876] ... [61585, 45923, 26539, 20911, 51779, 34695, 29023] +> a.len: 100000 | a: [1, 1, 2, 3, 4, 5, 7] ... [99991, 99992, 99994, 99994, 99998, 99998, 99999] +SPENT 24.082 ms in aswcc 100000 random +> a.len: 10000 | a: [7019, 733, 7844, 97, 1994, 2517, 8308] ... [1293, 4423, 8853, 6021, 9549, 4161, 3700] +> a.len: 10000 | a: [1, 1, 2, 2, 2, 3, 3] ... [9992, 9992, 9994, 9995, 9998, 9998, 9999] +SPENT 1.861 ms in aswcc 10000 random +> a.len: 1000 | a: [863, 875, 274, 733, 794, 496, 494] ... [530, 615, 371, 561, 800, 501, 585] +> a.len: 1000 | a: [2, 3, 4, 4, 5, 6, 6] ... [994, 994, 994, 994, 996, 997, 998] +SPENT 0.150 ms in aswcc 1000 random +> a.len: 100 | a: [95, 18, 58, 26, 6, 48, 67] ... [11, 80, 16, 60, 58, 71, 1] +> a.len: 100 | a: [1, 3, 4, 5, 5, 6, 6] ... [94, 95, 95, 97, 97, 98, 99] +SPENT 0.016 ms in aswcc 100 random +> a.len: 10 | a: [4, 6, 3, 8, 1, 4, 7] ... [8, 1, 4, 7, 5, 8, 4] +> a.len: 10 | a: [1, 3, 4, 4, 4, 5, 6] ... [4, 4, 5, 6, 7, 8, 8] +SPENT 0.005 ms in aswcc 10 random +-------------------------------- diff --git a/cmd/tools/bench/sorting.v b/cmd/tools/bench/sorting.v new file mode 100644 index 0000000000..b1ee389921 --- /dev/null +++ b/cmd/tools/bench/sorting.v @@ -0,0 +1,79 @@ +module main + +// This program compares the performance of the native C.qsort function, +// used by the a.sort/0 method, vs the more flexible and platform independent +// (at the cost of being slightly slower) C.vqsort_r (adapted from musl), +// used by the a.sort_with_compare_context/2 method. +// +// Usage: +// ./v -prod -cc gcc cmd/tools/bench/sorting.v +// for seqkind in increasing decreasing random; do +// for how in qsort aswcc; do +// for x in 10_000_000 1000_000 100_000 10_000 1000 100 10 ; do +// ./cmd/tools/bench/sorting $how $x $seqkind; +// done; +// done; +// echo --------------------------------; +// done > cmd/tools/bench/sorting.results.txt +import os +import rand +import benchmark + +fn my_context_compare_ints(const_a &int, const_b &int, context voidptr) int { + res := *const_a - *const_b + if res < 0 { + return -1 + } + if res > 0 { + return 1 + } + return 0 +} + +fn generate(kind string, mut a []int, maxn int) { + match kind { + 'random' { + for _ in 0 .. maxn { + a << rand.int_in_range(1, maxn) or { 0 } + } + } + 'increasing' { + for idx in 0 .. maxn { + a << idx + 1 + } + } + 'decreasing' { + for idx in 1 .. maxn { + a << maxn - idx + } + } + else {} + } +} + +fn sort(how string, mut a []int) { + match how { + 'aswcc' { + a.sort_with_compare_context(my_context_compare_ints, voidptr(3)) + } + 'qsort' { + a.sort() + } + else {} + } +} + +fn main() { + rand.seed([u32(0), 1]) + how := os.args[1] or { 'qsort' } + maxn := os.args[2] or { '1000' }.int() + kind := os.args[3] or { 'random' } + // println('> how: $how | maxn: $maxn') + mut a := []int{cap: maxn} + generate(kind, mut a, maxn) + println('> a.len: ${a.len:12} | a: ${a#[0..7]:50} ... ${a#[-7..]:-50}') + mut b := benchmark.start() + sort(how, mut a) + println('> a.len: ${a.len:12} | a: ${a#[0..7]:50} ... ${a#[-7..]:-50}') + b.measure('$how $maxn $kind') +}