Added SIMD versions of memCopy, memMove, memSet, and memCmp, used when SIMD is available. Fixed memCmpRef comparing bytes as signed. Updated Catch2 to 3.16.0. (#448)

This commit is contained in:
Branimir Karadžić
2026-10-07 19:20:00 -07:00
committed by GitHub
parent d86e4ea9d9
commit f56519b35d
4 changed files with 1776 additions and 617 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
#include <bx/debug.h>
#include <bx/readerwriter.h>
#include <bx/os.h>
#include <bx/simd_t.h>
#if !BX_CRT_NONE
# include <string.h> // memcpy, memmove, memset
@@ -102,6 +103,107 @@ namespace bx
}
}
namespace
{
#if BX_SIMD_AVX
typedef simd256_t SimdT;
#else
typedef simd128_t SimdT;
#endif // BX_SIMD_AVX
constexpr size_t kSimdSize = sizeof(SimdT);
#if BX_COMPILER_MSVC
typedef __unaligned uint32_t UnalignedU32;
typedef __unaligned uint64_t UnalignedU64;
#else
typedef uint32_t __attribute__( (aligned(1), may_alias) ) UnalignedU32;
typedef uint64_t __attribute__( (aligned(1), may_alias) ) UnalignedU64;
#endif // BX_COMPILER_MSVC
BX_FORCE_INLINE void copySmall(uint8_t* _dst, const uint8_t* _src, size_t _numBytes)
{
if (8 <= _numBytes)
{
const uint64_t head = *(const UnalignedU64*)_src;
const uint64_t tail = *(const UnalignedU64*)(_src + _numBytes - 8);
*(UnalignedU64*)_dst = head;
*(UnalignedU64*)(_dst + _numBytes - 8) = tail;
}
else if (4 <= _numBytes)
{
const uint32_t head = *(const UnalignedU32*)_src;
const uint32_t tail = *(const UnalignedU32*)(_src + _numBytes - 4);
*(UnalignedU32*)_dst = head;
*(UnalignedU32*)(_dst + _numBytes - 4) = tail;
}
else if (0 < _numBytes)
{
const uint8_t first = _src[0];
const uint8_t middle = _src[_numBytes/2];
const uint8_t last = _src[_numBytes - 1];
_dst[0] = first;
_dst[_numBytes/2] = middle;
_dst[_numBytes - 1] = last;
}
}
template<typename Ty>
BX_FORCE_INLINE void copyTwo(uint8_t* _dst, const uint8_t* _src, size_t _numBytes)
{
const Ty head = simd_ldu<Ty>(_src);
const Ty tail = simd_ldu<Ty>(_src + _numBytes - sizeof(Ty) );
simd_stu(_dst, head);
simd_stu(_dst + _numBytes - sizeof(Ty), tail);
}
BX_FORCE_INLINE void copyForward(uint8_t* _dst, const uint8_t* _src, size_t _begin, size_t _end)
{
size_t ii = _begin;
for (; ii + 4*kSimdSize <= _end; ii += 4*kSimdSize)
{
const SimdT a0 = simd_ldu<SimdT>(_src + ii);
const SimdT a1 = simd_ldu<SimdT>(_src + ii + kSimdSize);
const SimdT a2 = simd_ldu<SimdT>(_src + ii + 2*kSimdSize);
const SimdT a3 = simd_ldu<SimdT>(_src + ii + 3*kSimdSize);
simd_st(_dst + ii, a0);
simd_st(_dst + ii + kSimdSize, a1);
simd_st(_dst + ii + 2*kSimdSize, a2);
simd_st(_dst + ii + 3*kSimdSize, a3);
}
for (; ii + kSimdSize <= _end; ii += kSimdSize)
{
const SimdT a0 = simd_ldu<SimdT>(_src + ii);
simd_st(_dst + ii, a0);
}
}
BX_FORCE_INLINE int32_t cmpBytes(const uint8_t* _lhs, const uint8_t* _rhs, size_t _numBytes)
{
for (size_t ii = 0; ii < _numBytes; ++ii)
{
if (_lhs[ii] != _rhs[ii])
{
return int32_t(_lhs[ii]) - int32_t(_rhs[ii]);
}
}
return 0;
}
template<typename Ty>
BX_FORCE_INLINE bool memIsEqual(const uint8_t* _lhs, const uint8_t* _rhs)
{
const Ty lhs = simd_ldu<Ty>(_lhs);
const Ty rhs = simd_ldu<Ty>(_rhs);
const Ty diff = simd_xor(lhs, rhs);
return simd_test_zero(diff, diff);
}
} // namespace
void memCopyRef(void* _dst, const void* _src, size_t _numBytes)
{
uint8_t* dst = (uint8_t*)_dst;
@@ -113,13 +215,47 @@ namespace bx
}
}
void memCopySimd(void* _dst, const void* _src, size_t _numBytes)
{
uint8_t* dst = (uint8_t*)_dst;
const uint8_t* src = (const uint8_t*)_src;
if (16 > _numBytes)
{
copySmall(dst, src, _numBytes);
return;
}
if (32 >= _numBytes)
{
copyTwo<simd128_t>(dst, src, _numBytes);
return;
}
if (2*kSimdSize >= _numBytes)
{
copyTwo<SimdT>(dst, src, _numBytes);
return;
}
const SimdT head = simd_ldu<SimdT>(src);
const SimdT tail = simd_ldu<SimdT>(src + _numBytes - kSimdSize);
simd_stu(dst, head);
copyForward(dst, src, kSimdSize - (uintptr_t(dst) & (kSimdSize - 1) ), _numBytes);
simd_stu(dst + _numBytes - kSimdSize, tail);
}
void memCopy(void* _dst, const void* _src, size_t _numBytes)
{
#if BX_CRT_NONE
#if BX_SIMD_SUPPORTED
memCopySimd(_dst, _src, _numBytes);
#elif BX_CRT_NONE
memCopyRef(_dst, _src, _numBytes);
#else
::memcpy(_dst, _src, _numBytes);
#endif // BX_CRT_NONE
#endif // BX_*
}
void memCopy(
@@ -171,13 +307,79 @@ namespace bx
}
}
void memMoveSimd(void* _dst, const void* _src, size_t _numBytes)
{
uint8_t* dst = (uint8_t*)_dst;
const uint8_t* src = (const uint8_t*)_src;
if (0 == _numBytes
|| dst == src)
{
return;
}
if (16 > _numBytes)
{
copySmall(dst, src, _numBytes);
return;
}
if (32 >= _numBytes)
{
copyTwo<simd128_t>(dst, src, _numBytes);
return;
}
if (2*kSimdSize >= _numBytes)
{
copyTwo<SimdT>(dst, src, _numBytes);
return;
}
const SimdT head = simd_ldu<SimdT>(src);
const SimdT tail = simd_ldu<SimdT>(src + _numBytes - kSimdSize);
if (dst < src
|| dst >= src + _numBytes)
{
copyForward(dst, src, kSimdSize - (uintptr_t(dst) & (kSimdSize - 1) ), _numBytes);
}
else
{
size_t ii = _numBytes - (uintptr_t(dst + _numBytes) & (kSimdSize - 1) );
for (; ii >= 4*kSimdSize; ii -= 4*kSimdSize)
{
const SimdT a0 = simd_ldu<SimdT>(src + ii - kSimdSize);
const SimdT a1 = simd_ldu<SimdT>(src + ii - 2*kSimdSize);
const SimdT a2 = simd_ldu<SimdT>(src + ii - 3*kSimdSize);
const SimdT a3 = simd_ldu<SimdT>(src + ii - 4*kSimdSize);
simd_st(dst + ii - kSimdSize, a0);
simd_st(dst + ii - 2*kSimdSize, a1);
simd_st(dst + ii - 3*kSimdSize, a2);
simd_st(dst + ii - 4*kSimdSize, a3);
}
for (; ii >= kSimdSize; ii -= kSimdSize)
{
const SimdT a0 = simd_ldu<SimdT>(src + ii - kSimdSize);
simd_st(dst + ii - kSimdSize, a0);
}
}
simd_stu(dst, head);
simd_stu(dst + _numBytes - kSimdSize, tail);
}
void memMove(void* _dst, const void* _src, size_t _numBytes)
{
#if BX_CRT_NONE
#if BX_SIMD_SUPPORTED
memMoveSimd(_dst, _src, _numBytes);
#elif BX_CRT_NONE
memMoveRef(_dst, _src, _numBytes);
#else
::memmove(_dst, _src, _numBytes);
#endif // BX_CRT_NONE
#endif // BX_*
}
void memMove(
@@ -215,13 +417,79 @@ namespace bx
}
}
void memSetSimd(void* _dst, uint8_t _ch, size_t _numBytes)
{
uint8_t* dst = (uint8_t*)_dst;
if (16 > _numBytes)
{
const uint64_t value = uint64_t(_ch) * UINT64_C(0x0101010101010101);
if (8 <= _numBytes)
{
*(UnalignedU64*)dst = value;
*(UnalignedU64*)(dst + _numBytes - 8) = value;
}
else if (4 <= _numBytes)
{
*(UnalignedU32*)dst = uint32_t(value);
*(UnalignedU32*)(dst + _numBytes - 4) = uint32_t(value);
}
else if (0 < _numBytes)
{
dst[0] = _ch;
dst[_numBytes/2] = _ch;
dst[_numBytes - 1] = _ch;
}
return;
}
const uint32_t pattern = uint32_t(_ch) * 0x01010101u;
if (32 >= _numBytes)
{
const simd128_t value = simd_splat<simd128_t>(pattern);
simd_stu(dst, value);
simd_stu(dst + _numBytes - 16, value);
return;
}
const SimdT value = simd_splat<SimdT>(pattern);
simd_stu(dst, value);
simd_stu(dst + _numBytes - kSimdSize, value);
if (2*kSimdSize >= _numBytes)
{
return;
}
size_t ii = kSimdSize - (uintptr_t(dst) & (kSimdSize - 1) );
for (; ii + 4*kSimdSize <= _numBytes; ii += 4*kSimdSize)
{
simd_st(dst + ii, value);
simd_st(dst + ii + kSimdSize, value);
simd_st(dst + ii + 2*kSimdSize, value);
simd_st(dst + ii + 3*kSimdSize, value);
}
for (; ii + kSimdSize <= _numBytes; ii += kSimdSize)
{
simd_st(dst + ii, value);
}
}
void memSet(void* _dst, uint8_t _ch, size_t _numBytes)
{
#if BX_CRT_NONE
#if BX_SIMD_SUPPORTED
memSetSimd(_dst, _ch, _numBytes);
#elif BX_CRT_NONE
memSetRef(_dst, _ch, _numBytes);
#else
::memset(_dst, _ch, _numBytes);
#endif // BX_CRT_NONE
#endif // BX_*
}
void memSet(void* _dst, uint32_t _dstStride, uint8_t _ch, uint32_t _stride, uint32_t _num)
@@ -242,8 +510,8 @@ namespace bx
int32_t memCmpRef(const void* _lhs, const void* _rhs, size_t _numBytes)
{
const char* lhs = (const char*)_lhs;
const char* rhs = (const char*)_rhs;
const uint8_t* lhs = (const uint8_t*)_lhs;
const uint8_t* rhs = (const uint8_t*)_rhs;
for (
; 0 < _numBytes && *lhs == *rhs
; ++lhs, ++rhs, --_numBytes
@@ -251,16 +519,101 @@ namespace bx
{
}
return 0 == _numBytes ? 0 : *lhs - *rhs;
return 0 == _numBytes ? 0 : int32_t(*lhs) - int32_t(*rhs);
}
int32_t memCmpSimd(const void* _lhs, const void* _rhs, size_t _numBytes)
{
const uint8_t* lhs = (const uint8_t*)_lhs;
const uint8_t* rhs = (const uint8_t*)_rhs;
if (16 > _numBytes)
{
if (8 <= _numBytes)
{
if (*(const UnalignedU64*)lhs == *(const UnalignedU64*)rhs
&& *(const UnalignedU64*)(lhs + _numBytes - 8) == *(const UnalignedU64*)(rhs + _numBytes - 8) )
{
return 0;
}
}
else if (4 <= _numBytes)
{
if (*(const UnalignedU32*)lhs == *(const UnalignedU32*)rhs
&& *(const UnalignedU32*)(lhs + _numBytes - 4) == *(const UnalignedU32*)(rhs + _numBytes - 4) )
{
return 0;
}
}
return cmpBytes(lhs, rhs, _numBytes);
}
if (kSimdSize > _numBytes)
{
if (!memIsEqual<simd128_t>(lhs, rhs) )
{
return cmpBytes(lhs, rhs, 16);
}
const size_t last = _numBytes - 16;
return memIsEqual<simd128_t>(lhs + last, rhs + last)
? 0
: cmpBytes(lhs + last, rhs + last, 16)
;
}
size_t ii = 0;
for (; ii + 2*kSimdSize <= _numBytes; ii += 2*kSimdSize)
{
const SimdT lhs0 = simd_ldu<SimdT>(lhs + ii);
const SimdT rhs0 = simd_ldu<SimdT>(rhs + ii);
const SimdT lhs1 = simd_ldu<SimdT>(lhs + ii + kSimdSize);
const SimdT rhs1 = simd_ldu<SimdT>(rhs + ii + kSimdSize);
const SimdT diff0 = simd_xor(lhs0, rhs0);
const SimdT diff1 = simd_xor(lhs1, rhs1);
const SimdT diff = simd_or(diff0, diff1);
if (!simd_test_zero(diff, diff) )
{
return cmpBytes(lhs + ii, rhs + ii, 2*kSimdSize);
}
}
if (ii + kSimdSize <= _numBytes)
{
if (!memIsEqual<SimdT>(lhs + ii, rhs + ii) )
{
return cmpBytes(lhs + ii, rhs + ii, kSimdSize);
}
ii += kSimdSize;
}
if (ii < _numBytes)
{
const size_t last = _numBytes - kSimdSize;
if (!memIsEqual<SimdT>(lhs + last, rhs + last) )
{
return cmpBytes(lhs + last, rhs + last, kSimdSize);
}
}
return 0;
}
int32_t memCmp(const void* _lhs, const void* _rhs, size_t _numBytes)
{
#if BX_CRT_NONE
#if BX_SIMD_SUPPORTED
return memCmpSimd(_lhs, _rhs, _numBytes);
#elif BX_CRT_NONE
return memCmpRef(_lhs, _rhs, _numBytes);
#else
return ::memcmp(_lhs, _rhs, _numBytes);
#endif // BX_CRT_NONE
#endif // BX_*
}
///

View File

@@ -5,6 +5,24 @@
#include "test.h"
#include <bx/allocator.h>
#include <bx/rng.h>
#include <bx/string.h>
#include <string.h>
namespace bx
{
void memCopyRef(void* _dst, const void* _src, size_t _numBytes);
void memCopySimd(void* _dst, const void* _src, size_t _numBytes);
void memMoveRef(void* _dst, const void* _src, size_t _numBytes);
void memMoveSimd(void* _dst, const void* _src, size_t _numBytes);
void memSetRef(void* _dst, uint8_t _ch, size_t _numBytes);
void memSetSimd(void* _dst, uint8_t _ch, size_t _numBytes);
int32_t memCmpRef(const void* _lhs, const void* _rhs, size_t _numBytes);
int32_t memCmpSimd(const void* _lhs, const void* _rhs, size_t _numBytes);
}
TEST_CASE("memSet", "")
{
char temp[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
@@ -56,3 +74,272 @@ TEST_CASE("scatter/gather", "")
REQUIRE(0 == bx::memCmp(tmp1, str, 5) );
}
TEST_CASE("memCmpRef", "")
{
const uint8_t lhs[] = { 1, 2, 0x80 };
const uint8_t rhs[] = { 1, 2, 0x7f };
REQUIRE(0 < bx::memCmpRef(lhs, rhs, 3) );
REQUIRE(0 > bx::memCmpRef(rhs, lhs, 3) );
REQUIRE(0 == bx::memCmpRef(lhs, rhs, 2) );
REQUIRE(0 < ::memcmp(lhs, rhs, 3) );
}
namespace
{
constexpr uint32_t kMaxSize = 300;
constexpr uint32_t kGuard = 32;
constexpr uint32_t kMaxShift = 32;
constexpr uint32_t kBuffer = kGuard + kMaxShift + kMaxSize + kGuard;
constexpr uint8_t kGuardVal = 0xcd;
void fillRandom(uint8_t* _data, uint32_t _num, bx::RngMwc& _rng)
{
for (uint32_t ii = 0; ii < _num; ++ii)
{
_data[ii] = uint8_t(_rng.gen() );
}
}
int32_t sign(int32_t _value)
{
return (0 < _value) - (0 > _value);
}
uint32_t mismatch(const uint8_t* _lhs, const uint8_t* _rhs, uint32_t _size)
{
return sign(bx::memCmpSimd(_lhs, _rhs, _size) ) != sign(::memcmp(_lhs, _rhs, _size) );
}
} // namespace
TEST_CASE("memCopySimd", "")
{
bx::RngMwc rng;
uint8_t src[kBuffer];
uint8_t dst[kBuffer];
fillRandom(src, kBuffer, rng);
for (uint32_t size = 0; size <= kMaxSize; ++size)
{
for (uint32_t srcOffset = 0; srcOffset < kMaxShift; ++srcOffset)
{
for (uint32_t dstOffset = 0; dstOffset < kMaxShift; ++dstOffset)
{
::memset(dst, kGuardVal, kBuffer);
const uint8_t* from = src + kGuard + srcOffset;
uint8_t* to = dst + kGuard + dstOffset;
bx::memCopySimd(to, from, size);
REQUIRE(0 == ::memcmp(to, from, size) );
bool guardsIntact = true;
for (uint32_t ii = 0; ii < kBuffer; ++ii)
{
const bool inside = dst + ii >= to && dst + ii < to + size;
guardsIntact &= inside || kGuardVal == dst[ii];
}
REQUIRE(guardsIntact);
}
}
}
}
TEST_CASE("memMoveSimd", "")
{
bx::RngMwc rng;
uint8_t initial[kBuffer];
uint8_t actual[kBuffer];
uint8_t expected[kBuffer];
fillRandom(initial, kBuffer, rng);
const uint32_t kMaxDistance = 40;
for (uint32_t size = 0; size <= kMaxSize; size += 1 + size/32)
{
uint32_t numMismatch = 0;
for (uint32_t srcPos = kGuard; srcPos <= kGuard + kMaxDistance; ++srcPos)
{
for (uint32_t dstPos = kGuard; dstPos <= kGuard + kMaxDistance; ++dstPos)
{
if (srcPos + size + kGuard > kBuffer
|| dstPos + size + kGuard > kBuffer)
{
continue;
}
::memcpy(actual, initial, kBuffer);
::memcpy(expected, initial, kBuffer);
bx::memMoveSimd(actual + dstPos, actual + srcPos, size);
::memmove(expected + dstPos, expected + srcPos, size);
numMismatch += 0 != ::memcmp(actual, expected, kBuffer);
}
}
CAPTURE(size);
REQUIRE(0 == numMismatch);
}
for (uint32_t size = 0; size <= 100; ++size)
{
::memcpy(actual, initial, kBuffer);
::memcpy(expected, initial, kBuffer);
bx::memMoveSimd(actual + kBuffer - kGuard - size, actual + kGuard, size);
::memmove(expected + kBuffer - kGuard - size, expected + kGuard, size);
REQUIRE(0 == ::memcmp(actual, expected, kBuffer) );
bx::memMoveSimd(actual + kGuard, actual + kBuffer - kGuard - size, size);
::memmove(expected + kGuard, expected + kBuffer - kGuard - size, size);
REQUIRE(0 == ::memcmp(actual, expected, kBuffer) );
}
}
TEST_CASE("memSetSimd", "")
{
const uint8_t values[] = { 0x00, 0x01, 0x7f, 0x80, 0xa5, 0xff };
uint8_t dst[kBuffer];
for (uint8_t value : values)
{
for (uint32_t size = 0; size <= kMaxSize; ++size)
{
for (uint32_t offset = 0; offset < kMaxShift; ++offset)
{
::memset(dst, kGuardVal, kBuffer);
uint8_t* to = dst + kGuard + offset;
bx::memSetSimd(to, value, size);
bool filled = true;
for (uint32_t ii = 0; ii < kBuffer; ++ii)
{
const bool inside = dst + ii >= to && dst + ii < to + size;
filled &= (inside ? value : kGuardVal) == dst[ii];
}
REQUIRE(filled);
}
}
}
}
TEST_CASE("memCmpSimd", "")
{
bx::RngMwc rng;
uint8_t lhs[kBuffer];
uint8_t rhs[kBuffer];
fillRandom(lhs, kBuffer, rng);
const uint32_t offsets[] = { 0, 1, 7, 15, 31 };
for (uint32_t size = 0; size <= kMaxSize; ++size)
{
uint32_t numMismatch = 0;
for (uint32_t lhsOffset : offsets)
{
for (uint32_t rhsOffset : offsets)
{
uint8_t* left = lhs + kGuard + lhsOffset;
uint8_t* right = rhs + kGuard + rhsOffset;
fillRandom(rhs, kBuffer, rng);
::memcpy(right, left, size);
numMismatch += 0 != bx::memCmpSimd(left, right, size);
for (uint32_t pos = 0; pos < size; ++pos)
{
const uint8_t saved = right[pos];
right[pos] = uint8_t(left[pos] ^ 0x80);
numMismatch += mismatch(left, right, size);
numMismatch += mismatch(right, left, size);
right[pos] = uint8_t(left[pos] + 1);
numMismatch += mismatch(left, right, size);
if (pos + 1 < size)
{
right[size - 1] = uint8_t(left[size - 1] - 1);
numMismatch += mismatch(left, right, size);
right[size - 1] = left[size - 1];
}
right[pos] = saved;
}
}
}
CAPTURE(size);
REQUIRE(0 == numMismatch);
}
}
TEST_CASE("memSimd benchmark", "[.][!benchmark]")
{
const uint32_t sizes[] = { 7, 16, 64, 256, 4<<10, 64<<10, 1<<20 };
const uint32_t kMax = 1<<20;
static uint8_t s_src[kMax + 4096];
static uint8_t s_dst[kMax + 8192];
uint8_t* src = (uint8_t*)bx::alignPtr(s_src, 0, 4096) + 1;
uint8_t* dst = (uint8_t*)bx::alignPtr(s_dst, 0, 4096) + 2048 + 3;
::memset(src, 0x5a, kMax);
::memset(dst, 0x5a, kMax);
char name[64];
for (uint32_t size : sizes)
{
const uint32_t moveSize = size - 5;
bx::snprintf(name, sizeof(name), "::memcpy %u", size);
BENCHMARK(name) { ::memcpy(dst, src, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memCopyRef %u", size);
BENCHMARK(name) { bx::memCopyRef(dst, src, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memCopySimd %u", size);
BENCHMARK(name) { bx::memCopySimd(dst, src, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memCopy %u", size);
BENCHMARK(name) { bx::memCopy(dst, src, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "::memmove %u", size);
BENCHMARK(name) { ::memmove(dst, dst + 5, moveSize); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memMoveRef %u", size);
BENCHMARK(name) { bx::memMoveRef(dst, dst + 5, moveSize); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memMoveSimd %u", size);
BENCHMARK(name) { bx::memMoveSimd(dst, dst + 5, moveSize); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memMove %u", size);
BENCHMARK(name) { bx::memMove(dst, dst + 5, moveSize); return dst[0]; };
bx::snprintf(name, sizeof(name), "::memset %u", size);
BENCHMARK(name) { ::memset(dst, 0x5a, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memSetRef %u", size);
BENCHMARK(name) { bx::memSetRef(dst, 0x5a, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memSetSimd %u", size);
BENCHMARK(name) { bx::memSetSimd(dst, 0x5a, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "bx::memSet %u", size);
BENCHMARK(name) { bx::memSet(dst, 0x5a, size); return dst[0]; };
bx::snprintf(name, sizeof(name), "::memcmp %u", size);
BENCHMARK(name) { return ::memcmp(dst, src, size); };
bx::snprintf(name, sizeof(name), "bx::memCmpRef %u", size);
BENCHMARK(name) { return bx::memCmpRef(dst, src, size); };
bx::snprintf(name, sizeof(name), "bx::memCmpSimd %u", size);
BENCHMARK(name) { return bx::memCmpSimd(dst, src, size); };
bx::snprintf(name, sizeof(name), "bx::memCmp %u", size);
BENCHMARK(name) { return bx::memCmp(dst, src, size); };
}
}