From 0835f40ae25f97360dc393928796387d3cd6b16e Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Wed, 19 Apr 2017 00:43:37 +0100 Subject: erts: Add enif_phash2 and enif_phash2_ranged These allow one to hash VM terms from NIF code. --- erts/emulator/test/nif_SUITE.erl | 85 ++++++++++++++++++++++++++- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 19 ++++++ 2 files changed, 102 insertions(+), 2 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 693db42e58..780fe0a5ee 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -56,7 +56,9 @@ nif_is_process_alive/1, nif_is_port_alive/1, nif_term_to_binary/1, nif_binary_to_term/1, nif_port_command/1, - nif_snprintf/1 + nif_snprintf/1, + nif_phash2/1, + nif_phash2_ranged/1 ]). -export([many_args_100/100]). @@ -90,7 +92,9 @@ all() -> nif_is_process_alive, nif_is_port_alive, nif_term_to_binary, nif_binary_to_term, nif_port_command, - nif_snprintf]. + nif_snprintf, + nif_phash2, + nif_phash2_ranged]. groups() -> [{G, [], api_repeaters()} || G <- api_groups()] @@ -2610,6 +2614,81 @@ nif_snprintf(Config) -> <<"{{hello,world,-33},",0>> = format_term_nif(20,{{hello,world, -33}, 3.14, self()}), ok. +nif_phash2(Config) -> + ensure_lib_loaded(Config), + Terms = + [random_term() || _ <- lists:seq(1, 1000)], + + lists:foreach( + fun (Term) -> + HashValue = erlang:phash2(Term), + NifHashValue = phash2_nif(Term), + (HashValue =:= NifHashValue + orelse ct:fail("Expected: ~p\nActual: ~p", + [HashValue, NifHashValue])) + end, + Terms). + +nif_phash2_ranged(Config) -> + ensure_lib_loaded(Config), + RandomRangedTerms = + [{random_term(), rand:uniform((1 bsl 32) - 1)} + || _ <- lists:seq(1, 1000)], + + lists:foreach( + fun ({Term, Range}) -> + HashValue = erlang:phash2(Term, Range), + NifHashValue = phash2_ranged_nif(Term, Range), + (HashValue =:= NifHashValue + orelse ct:fail("Expected: ~p\nActual: ~p", + [HashValue, NifHashValue])) + end, + RandomRangedTerms), + + EdgeCaseTerm = random_term(), + EdgeCaseHashValue = erlang:phash2(EdgeCaseTerm, 1 bsl 32), + EdgeCaseNifHashValue = phash2_ranged_nif(EdgeCaseTerm, 0), + (EdgeCaseHashValue =:= EdgeCaseNifHashValue + orelse ct:fail("Expected: ~p\nActual: ~p", + [EdgeCaseHashValue, EdgeCaseNifHashValue])). + +-define(HALF_DBL_EPSILON, 1.1102230246251565e-16). % math:pow(2, -53) + +random_term() -> + case rand:uniform(6) of + 1 -> rand:uniform(1 bsl 27) - 1; % small + 2 -> (1 bsl 27) + rand:uniform(1 bsl 128); % big + 3 -> random_sign() * (rand:uniform() * ?HALF_DBL_EPSILON); % float + 4 -> random_binary(); + 5 -> random_pid(); + 6 -> + Length = rand:uniform(10), + List = [random_term() || _ <- lists:seq(1, Length)], + case rand:uniform(2) of + 1 -> + List; + 2 -> + list_to_tuple(List) + end + end. + +random_sign() -> + case rand:uniform(2) of + 1 -> -1.0; + 2 -> 1.0 + end. + +random_binary() -> + list_to_binary(random_bytes(rand:uniform(32) - 1)). + +random_bytes(0) -> + []; +random_bytes(N) when N > 0 -> + [rand:uniform(256) - 1 | random_bytes(N - 1)]. + +random_pid() -> + Processes = erlang:processes(), + lists:nth(rand:uniform(length(Processes)), Processes). %% The NIFs: lib_version() -> undefined. @@ -2621,6 +2700,8 @@ type_test() -> ?nif_stub. tuple_2_list(_) -> ?nif_stub. is_identical(_,_) -> ?nif_stub. compare(_,_) -> ?nif_stub. +phash2_nif(_) -> ?nif_stub. +phash2_ranged_nif(_, _) -> ?nif_stub. many_args_100(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. clone_bin(_) -> ?nif_stub. make_sub_bin(_,_,_) -> ?nif_stub. diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 8fe5ee809a..3bdd4d93c7 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -687,6 +687,23 @@ static ERL_NIF_TERM compare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return enif_make_int(env, enif_compare(argv[0],argv[1])); } +static ERL_NIF_TERM phash2_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + if (argc != 1) { + return enif_make_badarg(env); + } + return enif_make_ulong(env, enif_phash2(argv[0])); +} + +static ERL_NIF_TERM phash2_ranged_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + unsigned long range; + if (argc != 2 || !enif_get_ulong(env, argv[1], &range)) { + return enif_make_badarg(env); + } + return enif_make_ulong(env, enif_phash2_ranged(argv[0], range)); +} + static ERL_NIF_TERM many_args_100(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int i, k; @@ -2864,6 +2881,8 @@ static ErlNifFunc nif_funcs[] = {"tuple_2_list", 1, tuple_2_list}, {"is_identical",2,is_identical}, {"compare",2,compare}, + {"phash2_nif",1,phash2_nif}, + {"phash2_ranged_nif",2,phash2_ranged_nif}, {"many_args_100", 100, many_args_100}, {"clone_bin", 1, clone_bin}, {"make_sub_bin", 3, make_sub_bin}, -- cgit v1.2.3 From 1b37d0b010ea31b04b9d0a15d0bec9c75a013dc9 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Thu, 20 Apr 2017 21:35:54 +0100 Subject: erts: Remove enif_phash2_ranged --- erts/emulator/test/nif_SUITE.erl | 30 ++------------------------- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 10 --------- 2 files changed, 2 insertions(+), 38 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 780fe0a5ee..f868b018c9 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -57,8 +57,7 @@ nif_term_to_binary/1, nif_binary_to_term/1, nif_port_command/1, nif_snprintf/1, - nif_phash2/1, - nif_phash2_ranged/1 + nif_phash2/1 ]). -export([many_args_100/100]). @@ -93,8 +92,7 @@ all() -> nif_term_to_binary, nif_binary_to_term, nif_port_command, nif_snprintf, - nif_phash2, - nif_phash2_ranged]. + nif_phash2]. groups() -> [{G, [], api_repeaters()} || G <- api_groups()] @@ -2629,29 +2627,6 @@ nif_phash2(Config) -> end, Terms). -nif_phash2_ranged(Config) -> - ensure_lib_loaded(Config), - RandomRangedTerms = - [{random_term(), rand:uniform((1 bsl 32) - 1)} - || _ <- lists:seq(1, 1000)], - - lists:foreach( - fun ({Term, Range}) -> - HashValue = erlang:phash2(Term, Range), - NifHashValue = phash2_ranged_nif(Term, Range), - (HashValue =:= NifHashValue - orelse ct:fail("Expected: ~p\nActual: ~p", - [HashValue, NifHashValue])) - end, - RandomRangedTerms), - - EdgeCaseTerm = random_term(), - EdgeCaseHashValue = erlang:phash2(EdgeCaseTerm, 1 bsl 32), - EdgeCaseNifHashValue = phash2_ranged_nif(EdgeCaseTerm, 0), - (EdgeCaseHashValue =:= EdgeCaseNifHashValue - orelse ct:fail("Expected: ~p\nActual: ~p", - [EdgeCaseHashValue, EdgeCaseNifHashValue])). - -define(HALF_DBL_EPSILON, 1.1102230246251565e-16). % math:pow(2, -53) random_term() -> @@ -2701,7 +2676,6 @@ tuple_2_list(_) -> ?nif_stub. is_identical(_,_) -> ?nif_stub. compare(_,_) -> ?nif_stub. phash2_nif(_) -> ?nif_stub. -phash2_ranged_nif(_, _) -> ?nif_stub. many_args_100(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. clone_bin(_) -> ?nif_stub. make_sub_bin(_,_,_) -> ?nif_stub. diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 3bdd4d93c7..dac6d02e9d 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -695,15 +695,6 @@ static ERL_NIF_TERM phash2_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv return enif_make_ulong(env, enif_phash2(argv[0])); } -static ERL_NIF_TERM phash2_ranged_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - unsigned long range; - if (argc != 2 || !enif_get_ulong(env, argv[1], &range)) { - return enif_make_badarg(env); - } - return enif_make_ulong(env, enif_phash2_ranged(argv[0], range)); -} - static ERL_NIF_TERM many_args_100(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int i, k; @@ -2882,7 +2873,6 @@ static ErlNifFunc nif_funcs[] = {"is_identical",2,is_identical}, {"compare",2,compare}, {"phash2_nif",1,phash2_nif}, - {"phash2_ranged_nif",2,phash2_ranged_nif}, {"many_args_100", 100, many_args_100}, {"clone_bin", 1, clone_bin}, {"make_sub_bin", 3, make_sub_bin}, -- cgit v1.2.3 From 5ad95cca3f3c6802cd71fdd35dd397c3a37dc239 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Thu, 20 Apr 2017 21:53:54 +0100 Subject: erts: Fix random floats in enif_phash2 tests --- erts/emulator/test/nif_SUITE.erl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index f868b018c9..9127a5eae9 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -2627,13 +2627,11 @@ nif_phash2(Config) -> end, Terms). --define(HALF_DBL_EPSILON, 1.1102230246251565e-16). % math:pow(2, -53) - random_term() -> case rand:uniform(6) of 1 -> rand:uniform(1 bsl 27) - 1; % small 2 -> (1 bsl 27) + rand:uniform(1 bsl 128); % big - 3 -> random_sign() * (rand:uniform() * ?HALF_DBL_EPSILON); % float + 3 -> random_sign() * (rand:uniform() * (1 bsl 53)); % float 4 -> random_binary(); 5 -> random_pid(); 6 -> -- cgit v1.2.3 From d8756f8665a42effa2f3515369058cff4441abeb Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Thu, 20 Apr 2017 22:44:04 +0100 Subject: erts: Refactor enif_phash2 into enif_hash A more generic hashing function which can also hash terms based on `make_internal_hash'. --- erts/emulator/test/nif_SUITE.erl | 47 +++++++++++++++++++++++++-- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 20 +++++++++--- 2 files changed, 60 insertions(+), 7 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 9127a5eae9..e177fa4963 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -57,6 +57,7 @@ nif_term_to_binary/1, nif_binary_to_term/1, nif_port_command/1, nif_snprintf/1, + nif_internal_hash/1, nif_phash2/1 ]). @@ -92,6 +93,7 @@ all() -> nif_term_to_binary, nif_binary_to_term, nif_port_command, nif_snprintf, + nif_internal_hash, nif_phash2]. groups() -> @@ -2612,15 +2614,54 @@ nif_snprintf(Config) -> <<"{{hello,world,-33},",0>> = format_term_nif(20,{{hello,world, -33}, 3.14, self()}), ok. +nif_internal_hash(Config) -> + ensure_lib_loaded(Config), + Terms = + [random_term() || _ <- lists:seq(1, 5000)], + + % Unlike the phash2 hash, in this case we + % have nothing to compare to, so let's try + % and at least make sure the distribution + % isn't outright wrong, statistical nuances + % aside. + + OnesPerBit = + lists:foldl( + fun (Term, Acc) -> + NifHashValue = hash_nif(internal, Term), + lists:foldl( + fun (BitIndex, AccB) -> + BitValue = (NifHashValue band (1 bsl BitIndex)) bsr BitIndex, + dict:update_counter(BitIndex, BitValue, AccB) + end, + Acc, + lists:seq(0, 31)) + end, + dict:new(), + Terms), + + ExpectedNrOfOnes = length(Terms) div 2, + dict:fold( + fun (BitIndex, NrOfOnes, Acc) -> + RelativeDeviation = abs(NrOfOnes - ExpectedNrOfOnes) / ExpectedNrOfOnes, + (RelativeDeviation < 0.10 + orelse ct:fail("Unreasonable deviation on number of set bits (i=~p): " + "expected ~p, got ~p (relative dev. ~.3f)", + [BitIndex, ExpectedNrOfOnes, NrOfOnes, RelativeDeviation])), + Acc + end, + ok, + OnesPerBit). + nif_phash2(Config) -> ensure_lib_loaded(Config), Terms = - [random_term() || _ <- lists:seq(1, 1000)], + [random_term() || _ <- lists:seq(1, 5000)], lists:foreach( fun (Term) -> HashValue = erlang:phash2(Term), - NifHashValue = phash2_nif(Term), + NifHashValue = hash_nif(phash2, Term), (HashValue =:= NifHashValue orelse ct:fail("Expected: ~p\nActual: ~p", [HashValue, NifHashValue])) @@ -2673,7 +2714,7 @@ type_test() -> ?nif_stub. tuple_2_list(_) -> ?nif_stub. is_identical(_,_) -> ?nif_stub. compare(_,_) -> ?nif_stub. -phash2_nif(_) -> ?nif_stub. +hash_nif(_, _) -> ?nif_stub. many_args_100(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. clone_bin(_) -> ?nif_stub. make_sub_bin(_,_,_) -> ?nif_stub. diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index dac6d02e9d..9d65b9c09b 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -687,12 +687,24 @@ static ERL_NIF_TERM compare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return enif_make_int(env, enif_compare(argv[0],argv[1])); } -static ERL_NIF_TERM phash2_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - if (argc != 1) { + if (argc != 2) { + return enif_make_badarg(env); + } + + ErlNifHash type; + if (enif_is_identical(argv[0], enif_make_atom(env, "internal"))) { + type = ERL_NIF_INTERNAL_HASH; + } + else if (enif_is_identical(argv[0], enif_make_atom(env, "phash2"))) { + type = ERL_NIF_PHASH2; + } + else { return enif_make_badarg(env); } - return enif_make_ulong(env, enif_phash2(argv[0])); + + return enif_make_ulong(env, enif_hash(type, argv[1])); } static ERL_NIF_TERM many_args_100(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) @@ -2872,7 +2884,7 @@ static ErlNifFunc nif_funcs[] = {"tuple_2_list", 1, tuple_2_list}, {"is_identical",2,is_identical}, {"compare",2,compare}, - {"phash2_nif",1,phash2_nif}, + {"hash_nif",2,hash_nif}, {"many_args_100", 100, many_args_100}, {"clone_bin", 1, clone_bin}, {"make_sub_bin", 3, make_sub_bin}, -- cgit v1.2.3 From 21e1d879faaae2278a68200fe1085d7e73791fa0 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sat, 22 Apr 2017 17:22:32 +0100 Subject: erts: Support custom salt in enif_hash --- erts/emulator/test/nif_SUITE.erl | 6 +++--- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index e177fa4963..36af68be60 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -2628,7 +2628,7 @@ nif_internal_hash(Config) -> OnesPerBit = lists:foldl( fun (Term, Acc) -> - NifHashValue = hash_nif(internal, Term), + NifHashValue = hash_nif(internal, Term, 0), lists:foldl( fun (BitIndex, AccB) -> BitValue = (NifHashValue band (1 bsl BitIndex)) bsr BitIndex, @@ -2661,7 +2661,7 @@ nif_phash2(Config) -> lists:foreach( fun (Term) -> HashValue = erlang:phash2(Term), - NifHashValue = hash_nif(phash2, Term), + NifHashValue = hash_nif(phash2, Term, 0), (HashValue =:= NifHashValue orelse ct:fail("Expected: ~p\nActual: ~p", [HashValue, NifHashValue])) @@ -2714,7 +2714,7 @@ type_test() -> ?nif_stub. tuple_2_list(_) -> ?nif_stub. is_identical(_,_) -> ?nif_stub. compare(_,_) -> ?nif_stub. -hash_nif(_, _) -> ?nif_stub. +hash_nif(_Type, _Term, _Salt) -> ?nif_stub. many_args_100(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. clone_bin(_) -> ?nif_stub. make_sub_bin(_,_,_) -> ?nif_stub. diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 9d65b9c09b..2a80d48b62 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -689,7 +689,7 @@ static ERL_NIF_TERM compare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - if (argc != 2) { + if (argc != 3) { return enif_make_badarg(env); } @@ -704,7 +704,12 @@ static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] return enif_make_badarg(env); } - return enif_make_ulong(env, enif_hash(type, argv[1])); + unsigned long salt; + if (! enif_get_ulong(env, argv[2], &salt)) { + return enif_make_badarg(env); + } + + return enif_make_ulong(env, enif_hash(type, argv[1], salt)); } static ERL_NIF_TERM many_args_100(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) @@ -2884,7 +2889,7 @@ static ErlNifFunc nif_funcs[] = {"tuple_2_list", 1, tuple_2_list}, {"is_identical",2,is_identical}, {"compare",2,compare}, - {"hash_nif",2,hash_nif}, + {"hash_nif",3,hash_nif}, {"many_args_100", 100, many_args_100}, {"clone_bin", 1, clone_bin}, {"make_sub_bin", 3, make_sub_bin}, -- cgit v1.2.3 From c94506ccc003016db49fa2dbe5741196e071d45a Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sat, 22 Apr 2017 18:40:26 +0100 Subject: erts: Add test cases for salted enif_hash calls --- erts/emulator/test/nif_SUITE.erl | 148 ++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 41 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 36af68be60..6e4866bfd8 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -58,7 +58,9 @@ nif_port_command/1, nif_snprintf/1, nif_internal_hash/1, - nif_phash2/1 + nif_internal_hash_salted/1, + nif_phash2/1, + nif_phash2_salted/1 ]). -export([many_args_100/100]). @@ -94,7 +96,9 @@ all() -> nif_port_command, nif_snprintf, nif_internal_hash, - nif_phash2]. + nif_internal_hash_salted, + nif_phash2, + nif_phash2_salted]. groups() -> [{G, [], api_repeaters()} || G <- api_groups()] @@ -2616,57 +2620,119 @@ nif_snprintf(Config) -> nif_internal_hash(Config) -> ensure_lib_loaded(Config), - Terms = - [random_term() || _ <- lists:seq(1, 5000)], + HashValueBitSize = nif_hash_result_bitsize(internal), + Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), + HashValues = [hash_nif(internal, Term, 0) || Term <- Terms], + test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05). - % Unlike the phash2 hash, in this case we - % have nothing to compare to, so let's try - % and at least make sure the distribution - % isn't outright wrong, statistical nuances - % aside. +nif_internal_hash_salted(Config) -> + ensure_lib_loaded(Config), + test_salted_nif_hash(internal). + +nif_phash2(Config) -> + ensure_lib_loaded(Config), + HashValueBitSize = nif_hash_result_bitsize(phash2), + Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), + HashValues = + lists:map( + fun (Term) -> + HashValue = erlang:phash2(Term), + NifHashValue = hash_nif(phash2, Term, 0), + (HashValue =:= NifHashValue + orelse ct:fail("Expected: ~p\nActual: ~p", + [HashValue, NifHashValue])), + HashValue + end, + Terms), + test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05). + +nif_phash2_salted(Config) -> + ensure_lib_loaded(Config), + test_salted_nif_hash(phash2). + +test_salted_nif_hash(HashType) -> + HashValueBitSize = nif_hash_result_bitsize(HashType), + Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), + Salts = unique([random_uint32() || _ <- lists:seq(1, 100)]), + {HashValuesPerSalt, HashValuesPerTerm} = + lists:mapfoldl( + fun (Salt, Acc) -> + {HashValues, NewAcc} = + lists:mapfoldl( + fun (Term, AccB) -> + HashValue = hash_nif(HashType, Term, Salt), + NewAccB = dict:append(Term, HashValue, AccB), + {HashValue, NewAccB} + end, + Acc, + Terms), + {{Salt, HashValues}, NewAcc} + end, + dict:new(), + Salts), + + % Test per-salt hash distribution of different terms + lists:foreach( + fun ({_Salt, HashValues}) -> + test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05) + end, + HashValuesPerSalt), + % Test per-term hash distribution of different salts + dict:fold( + fun (_Term, HashValues, Acc) -> + % Be more tolerant of relative deviation, + % as there's fewer hash values here. + test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.30), + Acc + end, + ok, + HashValuesPerTerm). + +test_bit_distribution_fitness(Integers, BitSize, MaxRelativeDeviation) -> + MaxInteger = (1 bsl BitSize) - 1, OnesPerBit = lists:foldl( - fun (Term, Acc) -> - NifHashValue = hash_nif(internal, Term, 0), + fun (Integer, Acc) when Integer >= 0, Integer =< MaxInteger -> lists:foldl( fun (BitIndex, AccB) -> - BitValue = (NifHashValue band (1 bsl BitIndex)) bsr BitIndex, - dict:update_counter(BitIndex, BitValue, AccB) + BitValue = (Integer band (1 bsl BitIndex)) bsr BitIndex, + orddict:update_counter(BitIndex, BitValue, AccB) end, Acc, - lists:seq(0, 31)) + lists:seq(0, BitSize - 1)) end, - dict:new(), - Terms), + orddict:new(), + Integers), + + ExpectedNrOfOnes = length(Integers) div 2, + FailureText = + orddict:fold( + fun (BitIndex, NrOfOnes, Acc) -> + RelativeDeviation = abs(NrOfOnes - ExpectedNrOfOnes) / length(Integers), + case RelativeDeviation >= MaxRelativeDeviation of + false -> Acc; + true -> + [Acc, + io_lib:format( + "Unreasonable deviation on number of set bits (i=~p): " + "expected ~p, got ~p (relative dev. ~.3f)~n", + [BitIndex, ExpectedNrOfOnes, NrOfOnes, RelativeDeviation])] + end + end, + [], + OnesPerBit), - ExpectedNrOfOnes = length(Terms) div 2, - dict:fold( - fun (BitIndex, NrOfOnes, Acc) -> - RelativeDeviation = abs(NrOfOnes - ExpectedNrOfOnes) / ExpectedNrOfOnes, - (RelativeDeviation < 0.10 - orelse ct:fail("Unreasonable deviation on number of set bits (i=~p): " - "expected ~p, got ~p (relative dev. ~.3f)", - [BitIndex, ExpectedNrOfOnes, NrOfOnes, RelativeDeviation])), - Acc - end, - ok, - OnesPerBit). + (FailureText =:= [] orelse ct:fail(FailureText)). -nif_phash2(Config) -> - ensure_lib_loaded(Config), - Terms = - [random_term() || _ <- lists:seq(1, 5000)], +nif_hash_result_bitsize(internal) -> 32; +nif_hash_result_bitsize(phash2) -> 27. - lists:foreach( - fun (Term) -> - HashValue = erlang:phash2(Term), - NifHashValue = hash_nif(phash2, Term, 0), - (HashValue =:= NifHashValue - orelse ct:fail("Expected: ~p\nActual: ~p", - [HashValue, NifHashValue])) - end, - Terms). +unique(List) -> + lists:usort(List). + +random_uint32() -> + rand:uniform(1 bsl 32) - 1. random_term() -> case rand:uniform(6) of -- cgit v1.2.3 From da9abd24a93ae8fe174cdd38fc9699bbc45fdf56 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Sat, 22 Apr 2017 20:00:28 +0100 Subject: erts: Allow for easier future enif_hash expansion Allow for expanding support to 64-bit hashes without breaking the interface. --- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 2a80d48b62..a255c9f096 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -704,12 +704,12 @@ static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] return enif_make_badarg(env); } - unsigned long salt; - if (! enif_get_ulong(env, argv[2], &salt)) { + ErlNifUInt64 salt; + if (! enif_get_uint64(env, argv[2], &salt)) { return enif_make_badarg(env); } - return enif_make_ulong(env, enif_hash(type, argv[1], salt)); + return enif_make_uint64(env, enif_hash(type, argv[1], salt)); } static ERL_NIF_TERM many_args_100(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -- cgit v1.2.3 From 3d6b8e7fb68543713f1620a45b8a590ef4ed88a5 Mon Sep 17 00:00:00 2001 From: Guilherme Andrade Date: Mon, 24 Apr 2017 22:51:49 +0100 Subject: erts: Discontinue salted use of enif_hash/phash2 --- erts/emulator/test/nif_SUITE.erl | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 6e4866bfd8..8ad11d3bf3 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -59,8 +59,7 @@ nif_snprintf/1, nif_internal_hash/1, nif_internal_hash_salted/1, - nif_phash2/1, - nif_phash2_salted/1 + nif_phash2/1 ]). -export([many_args_100/100]). @@ -97,8 +96,7 @@ all() -> nif_snprintf, nif_internal_hash, nif_internal_hash_salted, - nif_phash2, - nif_phash2_salted]. + nif_phash2]. groups() -> [{G, [], api_repeaters()} || G <- api_groups()] @@ -2637,7 +2635,8 @@ nif_phash2(Config) -> lists:map( fun (Term) -> HashValue = erlang:phash2(Term), - NifHashValue = hash_nif(phash2, Term, 0), + Salt = random_uint32(), % phash2 should ignore salt + NifHashValue = hash_nif(phash2, Term, Salt), (HashValue =:= NifHashValue orelse ct:fail("Expected: ~p\nActual: ~p", [HashValue, NifHashValue])), @@ -2646,10 +2645,6 @@ nif_phash2(Config) -> Terms), test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05). -nif_phash2_salted(Config) -> - ensure_lib_loaded(Config), - test_salted_nif_hash(phash2). - test_salted_nif_hash(HashType) -> HashValueBitSize = nif_hash_result_bitsize(HashType), Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), -- cgit v1.2.3