mirror of
https://github.com/bkaradzic/bx.git
synced 2026-10-22 16:51:01 +00:00
346 lines
9.2 KiB
C++
346 lines
9.2 KiB
C++
/*
|
|
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
|
|
* License: https://github.com/bkaradzic/bx/blob/master/LICENSE
|
|
*/
|
|
|
|
#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 };
|
|
|
|
bx::memSet(temp, 0, 0);
|
|
REQUIRE(temp[0] == 1);
|
|
|
|
bx::memSet(temp, 0, 5);
|
|
REQUIRE(temp[0] == 0);
|
|
REQUIRE(temp[1] == 0);
|
|
REQUIRE(temp[2] == 0);
|
|
REQUIRE(temp[3] == 0);
|
|
REQUIRE(temp[4] == 0);
|
|
REQUIRE(temp[5] == 6);
|
|
}
|
|
|
|
TEST_CASE("memMove", "")
|
|
{
|
|
const char* original = "xxxxabvgd";
|
|
char str[] = { 'x', 'x', 'x', 'x', 'a', 'b', 'v', 'g', 'd' };
|
|
|
|
bx::memMove(&str[4], &str[4], 0);
|
|
REQUIRE(0 == bx::memCmp(str, original, 9) );
|
|
|
|
bx::memMove(&str[4], &str[4], 5);
|
|
REQUIRE(0 == bx::memCmp(str, original, 9) );
|
|
|
|
bx::memMove(str, &str[4], 5);
|
|
REQUIRE(0 == bx::memCmp(str, "abvgd", 5) );
|
|
|
|
bx::memMove(&str[4], str, 5);
|
|
REQUIRE(str[4] == 'a' );
|
|
|
|
bx::memSet(str, 'x', 4);
|
|
REQUIRE(0 == bx::memCmp(str, original, 9) );
|
|
}
|
|
|
|
TEST_CASE("scatter/gather", "")
|
|
{
|
|
const char* str = "a\0b\0v\0g\0d";
|
|
|
|
char tmp0[64];
|
|
bx::gather(tmp0, str, 2, 1, 5);
|
|
REQUIRE(0 == bx::memCmp(tmp0, "abvgd", 5) );
|
|
|
|
char tmp1[64];
|
|
bx::scatter(tmp1, 2, tmp0, 1, 5);
|
|
bx::memSet(&tmp1[1], 2, 0, 1, 5);
|
|
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); };
|
|
}
|
|
}
|