diff --git a/configure.ac b/configure.ac index 59dfdae3a4..a39afef720 100644 --- a/configure.ac +++ b/configure.ac @@ -1579,9 +1579,10 @@ AC_CONFIG_FILES([doc/Makefile doc/man/Makefile doc/misc/Makefile]) -# Unit Tests +# Unit tests and benchmarks AC_CONFIG_FILES([tests/Makefile + tests/bench/Makefile tests/isc/Makefile tests/dns/Makefile tests/ns/Makefile diff --git a/tests/Makefile.am b/tests/Makefile.am index 8b5dcbe842..3a1a0241fe 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -12,5 +12,6 @@ LDADD += \ $(LIBDNS_LIBS) \ $(LIBNS_LIBS) -SUBDIRS = libtest isc dns ns isccfg irs +SUBDIRS = libtest isc dns ns isccfg irs bench + check_PROGRAMS = diff --git a/tests/bench/.gitignore b/tests/bench/.gitignore new file mode 100644 index 0000000000..c9c3825ad1 --- /dev/null +++ b/tests/bench/.gitignore @@ -0,0 +1 @@ +ascii diff --git a/tests/bench/Makefile.am b/tests/bench/Makefile.am new file mode 100644 index 0000000000..171db37b39 --- /dev/null +++ b/tests/bench/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/Makefile.top + +AM_CPPFLAGS += \ + $(LIBISC_CFLAGS) + +LDADD += \ + $(LIBISC_LIBS) + +noinst_PROGRAMS = \ + ascii diff --git a/tests/bench/ascii.c b/tests/bench/ascii.c new file mode 100644 index 0000000000..dbc62d3efb --- /dev/null +++ b/tests/bench/ascii.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define SIZE (1024 * 1024) + +typedef void +copy_fn(void *a, void *b, unsigned len); + +static void +time_it(copy_fn *copier, void *a, void *b, const char *name) { + isc_time_t start; + isc_time_now_hires(&start); + + copier(a, b, SIZE); + + isc_time_t finish; + isc_time_now_hires(&finish); + + uint64_t microseconds = isc_time_microdiff(&finish, &start); + printf("%f for %s\n", (double)microseconds / 1000000.0, name); +} + +static void +copy_raw(void *a, void *b, unsigned size) { + memmove(a, b, size); +} + +static void +copy_toupper(void *va, void *vb, unsigned size) { + uint8_t *a = va, *b = vb; + while (size-- > 0) { + *a++ = isc_ascii_toupper(*b++); + } +} + +static void +copy_tolower8(void *a, void *b, unsigned size) { + isc_ascii_lowercopy(a, b, size); +} + +#define TOLOWER(c) ((c) + ('a' - 'A') * (((c) >= 'A') ^ ((c) > 'Z'))) + +static void +copy_tolower1(void *va, void *vb, unsigned size) { + for (uint8_t *a = va, *b = vb; size-- > 0; a++, b++) { + *a = TOLOWER(*b); + } +} + +static bool +cmp_tolower1(void *va, void *vb, unsigned size) { + for (uint8_t *a = va, *b = vb; size-- > 0; a++, b++) { + if (TOLOWER(*a) != TOLOWER(*b)) { + return false; + } + } + return true; +} + +static bool oldskool_result; + +static void +cmp_oldskool(void *va, void *vb, unsigned size) { + uint8_t *a = va, *b = vb, c; + + while (size > 3) { + c = isc_ascii_tolower(a[0]); + if (c != isc_ascii_tolower(b[0])) { + goto diff; + } + c = isc_ascii_tolower(a[1]); + if (c != isc_ascii_tolower(b[1])) { + goto diff; + } + c = isc_ascii_tolower(a[2]); + if (c != isc_ascii_tolower(b[2])) { + goto diff; + } + c = isc_ascii_tolower(a[3]); + if (c != isc_ascii_tolower(b[3])) { + goto diff; + } + size -= 4; + a += 4; + b += 4; + } + while (size-- > 0) { + c = isc_ascii_tolower(*a++); + if (c != isc_ascii_tolower(*b++)) { + goto diff; + } + } + oldskool_result = true; + return; +diff: + oldskool_result = false; + return; +} + +static bool tolower1_result; + +static void +vcmp_tolower1(void *a, void *b, unsigned size) { + tolower1_result = cmp_tolower1(a, b, size); +} + +static bool swar_result; + +static void +cmp_swar(void *a, void *b, unsigned size) { + swar_result = isc_ascii_lowerequal(a, b, size); +} + +static bool chunk_result; +static unsigned chunk_size; + +static void +cmp_chunks1(void *va, void *vb, unsigned size) { + uint8_t *a = va, *b = vb; + + chunk_result = false; + while (size >= chunk_size) { + if (!cmp_tolower1(a, b, chunk_size)) { + return; + } + size -= chunk_size; + a += chunk_size; + b += chunk_size; + } + chunk_result = cmp_tolower1(a, b, size); +} + +static void +cmp_chunks8(void *va, void *vb, unsigned size) { + uint8_t *a = va, *b = vb; + + while (size >= chunk_size) { + if (!isc_ascii_lowerequal(a, b, chunk_size)) { + goto diff; + } + size -= chunk_size; + a += chunk_size; + b += chunk_size; + } + chunk_result = isc_ascii_lowerequal(a, b, size); + return; +diff: + chunk_result = false; + return; +} + +static void +cmp_oldchunks(void *va, void *vb, unsigned size) { + uint8_t *a = va, *b = vb; + + while (size >= chunk_size) { + cmp_oldskool(a, b, chunk_size); + if (!oldskool_result) { + return; + } + size -= chunk_size; + a += chunk_size; + b += chunk_size; + } + cmp_oldskool(a, b, size); +} + +int +main(void) { + static uint8_t bytes[SIZE]; + + isc_random_buf(bytes, SIZE); + + static uint8_t raw_dest[SIZE]; + time_it(copy_raw, raw_dest, bytes, "memmove"); + + static uint8_t toupper_dest[SIZE]; + time_it(copy_toupper, toupper_dest, bytes, "toupper"); + + static uint8_t tolower1_dest[SIZE]; + time_it(copy_tolower1, tolower1_dest, bytes, "tolower1"); + + static uint8_t tolower8_dest[SIZE]; + time_it(copy_tolower8, tolower8_dest, bytes, "tolower8"); + + time_it(cmp_oldskool, toupper_dest, tolower1_dest, "oldskool"); + printf("-> %s\n", oldskool_result ? "same" : "WAT"); + + time_it(vcmp_tolower1, tolower1_dest, tolower8_dest, "tolower1"); + printf("-> %s\n", tolower1_result ? "same" : "WAT"); + + time_it(cmp_swar, toupper_dest, tolower8_dest, "swar"); + printf("-> %s\n", swar_result ? "same" : "WAT"); + + for (chunk_size = 3; chunk_size <= 15; chunk_size += 2) { + time_it(cmp_chunks1, toupper_dest, raw_dest, "chunks1"); + printf("%u -> %s\n", chunk_size, chunk_result ? "same" : "WAT"); + time_it(cmp_chunks8, toupper_dest, raw_dest, "chunks8"); + printf("%u -> %s\n", chunk_size, chunk_result ? "same" : "WAT"); + time_it(cmp_oldchunks, toupper_dest, raw_dest, "oldchunks"); + printf("%u -> %s\n", chunk_size, + oldskool_result ? "same" : "WAT"); + } +} diff --git a/tests/isc/Makefile.am b/tests/isc/Makefile.am index 59b1acd79e..a9fd5a6137 100644 --- a/tests/isc/Makefile.am +++ b/tests/isc/Makefile.am @@ -10,6 +10,7 @@ LDADD += \ $(LIBUV_LIBS) check_PROGRAMS = \ + ascii_test \ aes_test \ async_test \ buffer_test \ diff --git a/tests/isc/ascii_test.c b/tests/isc/ascii_test.c new file mode 100644 index 0000000000..43be4ce6bc --- /dev/null +++ b/tests/isc/ascii_test.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include + +#include + +const char *same[][2] = { + { + "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + }, + { + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ", + }, + { + "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + }, + { + "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + }, + { + "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVxXyYzZ", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvxxyyzz", + }, + { + "WwW.ExAmPlE.OrG", + "wWw.eXaMpLe.oRg", + }, + { + "_SIP.tcp.example.org", + "_sip.TCP.example.org", + }, + { + "bind-USERS.lists.example.org", + "bind-users.lists.example.org", + }, + { + "a0123456789.example.org", + "A0123456789.example.org", + }, + { + "\\000.example.org", + "\\000.example.org", + }, + { + "wWw.\\000.isc.org", + "www.\\000.isc.org", + }, + { + "\255.example.org", + "\255.example.ORG", + } +}; + +struct { + const char *a, *b; + int cmp; +} diff[] = { + { "foo", "bar", +1 }, + { "bar", "foo", -1 }, + { "foosuffix", "barsuffix", +1 }, + { "barsuffix", "foosuffix", -1 }, + { "prefixfoo", "prefixbar", +1 }, + { "prefixbar", "prefixfoo", -1 }, +}; + +ISC_RUN_TEST_IMPL(upperlower) { + UNUSED(state); + + for (size_t n = 0; n < ARRAY_SIZE(same); n++) { + const char *a = same[n][0]; + const char *b = same[n][1]; + for (size_t i = 0; a[i] != '\0' && b[i] != '\0'; i++) { + assert_true(isc_ascii_toupper(a[i]) == (uint8_t)a[i] || + isc_ascii_tolower(a[i]) == (uint8_t)a[i]); + assert_true(isc_ascii_toupper(b[i]) == (uint8_t)b[i] || + isc_ascii_tolower(b[i]) == (uint8_t)b[i]); + assert_true(isc_ascii_toupper(a[i]) == + isc_ascii_toupper(b[i])); + assert_true(isc_ascii_tolower(a[i]) == + isc_ascii_tolower(b[i])); + } + } +} + +ISC_RUN_TEST_IMPL(lowerequal) { + for (size_t n = 0; n < ARRAY_SIZE(same); n++) { + const uint8_t *a = (void *)same[n][0]; + const uint8_t *b = (void *)same[n][1]; + unsigned len = (unsigned)strlen(same[n][0]); + assert_true(isc_ascii_lowerequal(a, b, len)); + } + for (size_t n = 0; n < ARRAY_SIZE(diff); n++) { + const uint8_t *a = (void *)diff[n].a; + const uint8_t *b = (void *)diff[n].b; + unsigned len = (unsigned)strlen(diff[n].a); + assert_true(!isc_ascii_lowerequal(a, b, len)); + } +} + +ISC_RUN_TEST_IMPL(lowercmp) { + for (size_t n = 0; n < ARRAY_SIZE(same); n++) { + const uint8_t *a = (void *)same[n][0]; + const uint8_t *b = (void *)same[n][1]; + unsigned len = (unsigned)strlen(same[n][0]); + assert_true(isc_ascii_lowercmp(a, b, len) == 0); + } + for (size_t n = 0; n < ARRAY_SIZE(diff); n++) { + const uint8_t *a = (void *)diff[n].a; + const uint8_t *b = (void *)diff[n].b; + unsigned len = (unsigned)strlen(diff[n].a); + assert_true(isc_ascii_lowercmp(a, b, len) == diff[n].cmp); + } +} + +ISC_RUN_TEST_IMPL(exhaustive) { + for (uint64_t ab = 0; ab < (1 << 16); ab++) { + uint8_t a = ab >> 8; + uint8_t b = ab & 0xFF; + uint64_t abc = tolower(a) << 8 | tolower(b); + uint64_t abi = isc_ascii_tolower(a) << 8 | isc_ascii_tolower(b); + uint64_t ab1 = isc__ascii_tolower1(a) << 8 | + isc__ascii_tolower1(b); + uint64_t ab8 = isc__ascii_tolower8(ab); + /* each byte individually matches ctype.h */ + assert_int_equal(tolower(a), isc_ascii_tolower(a)); + assert_int_equal(tolower(a), isc__ascii_tolower1(a)); + assert_int_equal(tolower(a), isc__ascii_tolower8(a)); + assert_int_equal(tolower(b), isc_ascii_tolower(b)); + assert_int_equal(tolower(b), isc__ascii_tolower1(b)); + assert_int_equal(tolower(b), isc__ascii_tolower8(b)); + /* two lanes of SWAR match other implementations */ + assert_int_equal(ab8, abc); + assert_int_equal(ab8, abi); + assert_int_equal(ab8, ab1); + /* check lack of overflow */ + assert_int_equal(ab8 >> 16, 0); + /* all lanes of SWAR work */ + assert_int_equal(isc__ascii_tolower8(ab << 8), abc << 8); + assert_int_equal(isc__ascii_tolower8(ab << 16), abc << 16); + assert_int_equal(isc__ascii_tolower8(ab << 24), abc << 24); + assert_int_equal(isc__ascii_tolower8(ab << 32), abc << 32); + assert_int_equal(isc__ascii_tolower8(ab << 40), abc << 40); + assert_int_equal(isc__ascii_tolower8(ab << 48), abc << 48); + } +} + +ISC_TEST_LIST_START +ISC_TEST_ENTRY(upperlower) +ISC_TEST_ENTRY(lowerequal) +ISC_TEST_ENTRY(lowercmp) +ISC_TEST_ENTRY(exhaustive) +ISC_TEST_LIST_END + +ISC_TEST_MAIN