mirror of https://github.com/status-im/leopard.git
607 lines
18 KiB
C++
607 lines
18 KiB
C++
/*
|
|
Copyright (c) 2017 Christopher A. Taylor. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
* Neither the name of Leopard-RS nor the names of its contributors may be
|
|
used to endorse or promote products derived from this software without
|
|
specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "../LeopardCommon.h"
|
|
#include "../LeopardFF8.h"
|
|
#include "../LeopardFF16.h"
|
|
#include "../leopard.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <iostream>
|
|
#include <string>
|
|
using namespace std;
|
|
|
|
//#define TEST_DATA_ALL_SAME
|
|
|
|
struct TestParameters
|
|
{
|
|
#ifdef LEO_HAS_FF16
|
|
unsigned original_count = 32768; // under 65536
|
|
unsigned recovery_count = 32768; // under 65536 - original_count
|
|
#else
|
|
unsigned original_count = 128; // under 65536
|
|
unsigned recovery_count = 128; // under 65536 - original_count
|
|
#endif
|
|
unsigned buffer_bytes = 64000; // multiple of 64 bytes
|
|
unsigned loss_count = 32768; // some fraction of original_count
|
|
unsigned seed = 2;
|
|
bool multithreaded = true;
|
|
};
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Windows
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#ifndef _WINSOCKAPI_
|
|
#define DID_DEFINE_WINSOCKAPI
|
|
#define _WINSOCKAPI_
|
|
#endif
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#ifndef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0601 /* Windows 7+ */
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#ifdef DID_DEFINE_WINSOCKAPI
|
|
#undef _WINSOCKAPI_
|
|
#undef DID_DEFINE_WINSOCKAPI
|
|
#endif
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Threads
|
|
|
|
static bool SetCurrentThreadPriority()
|
|
{
|
|
#ifdef _WIN32
|
|
return 0 != ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
|
|
#else
|
|
return -1 != nice(2);
|
|
#endif
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Timing
|
|
|
|
static uint64_t GetTimeUsec()
|
|
{
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER timeStamp = {};
|
|
if (!::QueryPerformanceCounter(&timeStamp))
|
|
return 0;
|
|
static double PerfFrequencyInverse = 0.;
|
|
if (PerfFrequencyInverse == 0.)
|
|
{
|
|
LARGE_INTEGER freq = {};
|
|
if (!::QueryPerformanceFrequency(&freq) || freq.QuadPart == 0)
|
|
return 0;
|
|
PerfFrequencyInverse = 1000000. / (double)freq.QuadPart;
|
|
}
|
|
return (uint64_t)(PerfFrequencyInverse * timeStamp.QuadPart);
|
|
#else
|
|
struct timeval tv;
|
|
gettimeofday(&tv, nullptr);
|
|
return 1000000 * tv.tv_sec + tv.tv_usec;
|
|
#endif // _WIN32
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// PCG PRNG
|
|
// From http://www.pcg-random.org/
|
|
|
|
class PCGRandom
|
|
{
|
|
public:
|
|
inline void Seed(uint64_t y, uint64_t x = 0)
|
|
{
|
|
State = 0;
|
|
Inc = (y << 1u) | 1u;
|
|
Next();
|
|
State += x;
|
|
Next();
|
|
}
|
|
|
|
inline uint32_t Next()
|
|
{
|
|
const uint64_t oldstate = State;
|
|
State = oldstate * UINT64_C(6364136223846793005) + Inc;
|
|
const uint32_t xorshifted = (uint32_t)(((oldstate >> 18) ^ oldstate) >> 27);
|
|
const uint32_t rot = oldstate >> 59;
|
|
return (xorshifted >> rot) | (xorshifted << ((uint32_t)(-(int32_t)rot) & 31));
|
|
}
|
|
|
|
uint64_t State = 0, Inc = 0;
|
|
};
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Self-Checking Packet
|
|
|
|
static void WriteRandomSelfCheckingPacket(PCGRandom& prng, void* packet, unsigned bytes)
|
|
{
|
|
uint8_t* buffer = (uint8_t*)packet;
|
|
#ifdef TEST_DATA_ALL_SAME
|
|
if (bytes != 0)
|
|
#else
|
|
if (bytes < 16)
|
|
#endif
|
|
{
|
|
LEO_DEBUG_ASSERT(bytes >= 2);
|
|
buffer[0] = (uint8_t)prng.Next();
|
|
for (unsigned i = 1; i < bytes; ++i)
|
|
{
|
|
buffer[i] = buffer[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t crc = bytes;
|
|
*(uint32_t*)(buffer + 4) = bytes;
|
|
for (unsigned i = 8; i < bytes; ++i)
|
|
{
|
|
uint8_t v = (uint8_t)prng.Next();
|
|
buffer[i] = v;
|
|
crc = (crc << 3) | (crc >> (32 - 3));
|
|
crc += v;
|
|
}
|
|
*(uint32_t*)buffer = crc;
|
|
}
|
|
}
|
|
|
|
static bool CheckPacket(const void* packet, unsigned bytes)
|
|
{
|
|
uint8_t* buffer = (uint8_t*)packet;
|
|
#ifdef TEST_DATA_ALL_SAME
|
|
if (bytes != 0)
|
|
#else
|
|
if (bytes < 16)
|
|
#endif
|
|
{
|
|
if (bytes < 2)
|
|
return false;
|
|
|
|
uint8_t v = buffer[0];
|
|
for (unsigned i = 1; i < bytes; ++i)
|
|
{
|
|
if (buffer[i] != v)
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t crc = bytes;
|
|
uint32_t readBytes = *(uint32_t*)(buffer + 4);
|
|
if (readBytes != bytes)
|
|
return false;
|
|
for (unsigned i = 8; i < bytes; ++i)
|
|
{
|
|
uint8_t v = buffer[i];
|
|
crc = (crc << 3) | (crc >> (32 - 3));
|
|
crc += v;
|
|
}
|
|
uint32_t readCRC = *(uint32_t*)buffer;
|
|
if (readCRC != crc)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// FunctionTimer
|
|
|
|
class FunctionTimer
|
|
{
|
|
public:
|
|
FunctionTimer(const std::string& name)
|
|
{
|
|
FunctionName = name;
|
|
}
|
|
void BeginCall()
|
|
{
|
|
LEO_DEBUG_ASSERT(t0 == 0);
|
|
t0 = GetTimeUsec();
|
|
}
|
|
void EndCall()
|
|
{
|
|
LEO_DEBUG_ASSERT(t0 != 0);
|
|
const uint64_t t1 = GetTimeUsec();
|
|
const uint64_t delta = t1 - t0;
|
|
if (++Invokations == 1)
|
|
MaxCallUsec = MinCallUsec = delta;
|
|
else if (MaxCallUsec < delta)
|
|
MaxCallUsec = delta;
|
|
else if (MinCallUsec > delta)
|
|
MinCallUsec = delta;
|
|
TotalUsec += delta;
|
|
t0 = 0;
|
|
}
|
|
void Reset()
|
|
{
|
|
LEO_DEBUG_ASSERT(t0 == 0);
|
|
t0 = 0;
|
|
Invokations = 0;
|
|
TotalUsec = 0;
|
|
}
|
|
void Print(unsigned trials)
|
|
{
|
|
cout << FunctionName << " called " << Invokations / (float)trials << " times per trial. " << TotalUsec / (double)Invokations << " usec avg. " << TotalUsec / (float)trials << " usec for each of " << trials << " trials" << endl;
|
|
}
|
|
|
|
uint64_t t0 = 0;
|
|
uint64_t Invokations = 0;
|
|
uint64_t TotalUsec = 0;
|
|
uint64_t MaxCallUsec = 0;
|
|
uint64_t MinCallUsec = 0;
|
|
std::string FunctionName;
|
|
};
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Utility: Deck Shuffling function
|
|
|
|
/*
|
|
Given a PRNG, generate a deck of cards in a random order.
|
|
The deck will contain elements with values between 0 and count - 1.
|
|
*/
|
|
|
|
static void ShuffleDeck16(PCGRandom &prng, uint16_t * LEO_RESTRICT deck, uint32_t count)
|
|
{
|
|
deck[0] = 0;
|
|
|
|
// If we can unroll 4 times,
|
|
if (count <= 256)
|
|
{
|
|
for (uint32_t ii = 1;;)
|
|
{
|
|
uint32_t jj, rv = prng.Next();
|
|
|
|
// 8-bit unroll
|
|
switch (count - ii)
|
|
{
|
|
default:
|
|
jj = (uint8_t)rv % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
jj = (uint8_t)(rv >> 8) % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
jj = (uint8_t)(rv >> 16) % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
jj = (uint8_t)(rv >> 24) % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
break;
|
|
|
|
case 3:
|
|
jj = (uint8_t)rv % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
case 2:
|
|
jj = (uint8_t)(rv >> 8) % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
case 1:
|
|
jj = (uint8_t)(rv >> 16) % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
case 0:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For each deck entry,
|
|
for (uint32_t ii = 1;;)
|
|
{
|
|
uint32_t jj, rv = prng.Next();
|
|
|
|
// 16-bit unroll
|
|
switch (count - ii)
|
|
{
|
|
default:
|
|
jj = (uint16_t)rv % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
jj = (uint16_t)(rv >> 16) % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
++ii;
|
|
break;
|
|
|
|
case 1:
|
|
jj = (uint16_t)rv % ii;
|
|
deck[ii] = deck[jj];
|
|
deck[jj] = ii;
|
|
case 0:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Benchmark
|
|
|
|
static bool Benchmark(const TestParameters& params)
|
|
{
|
|
const unsigned kTrials = params.original_count > 8000 ? 1 : 10;
|
|
|
|
std::vector<uint8_t*> original_data(params.original_count);
|
|
|
|
const unsigned encode_work_count = leo_encode_work_count(params.original_count, params.recovery_count);
|
|
const unsigned decode_work_count = leo_decode_work_count(params.original_count, params.recovery_count);
|
|
|
|
std::vector<uint8_t*> encode_work_data(encode_work_count);
|
|
std::vector<uint8_t*> decode_work_data(decode_work_count);
|
|
|
|
FunctionTimer t_mem_alloc("memory_allocation");
|
|
FunctionTimer t_leo_encode("leo_encode");
|
|
FunctionTimer t_leo_decode("leo_decode");
|
|
FunctionTimer t_mem_free("memory_free");
|
|
|
|
const uint64_t total_bytes = (uint64_t)params.buffer_bytes * params.original_count;
|
|
|
|
for (unsigned trial = 0; trial < kTrials; ++trial)
|
|
{
|
|
// Allocate memory:
|
|
|
|
t_mem_alloc.BeginCall();
|
|
for (unsigned i = 0, count = params.original_count; i < count; ++i)
|
|
original_data[i] = leopard::SIMDSafeAllocate(params.buffer_bytes);
|
|
for (unsigned i = 0, count = encode_work_count; i < count; ++i)
|
|
encode_work_data[i] = leopard::SIMDSafeAllocate(params.buffer_bytes);
|
|
for (unsigned i = 0, count = decode_work_count; i < count; ++i)
|
|
decode_work_data[i] = leopard::SIMDSafeAllocate(params.buffer_bytes);
|
|
t_mem_alloc.EndCall();
|
|
|
|
// Generate data:
|
|
|
|
PCGRandom prng;
|
|
prng.Seed(params.seed, trial);
|
|
|
|
for (unsigned i = 0; i < params.original_count; ++i)
|
|
WriteRandomSelfCheckingPacket(prng, original_data[i], params.buffer_bytes);
|
|
|
|
// Encode:
|
|
|
|
t_leo_encode.BeginCall();
|
|
LeopardResult encodeResult = leo_encode(
|
|
params.buffer_bytes,
|
|
params.original_count,
|
|
params.recovery_count,
|
|
encode_work_count,
|
|
(void**)&original_data[0],
|
|
(void**)&encode_work_data[0], // recovery data written here
|
|
params.multithreaded ? LeopardFlags_Multithreaded : LeopardFlags_Defaults
|
|
);
|
|
t_leo_encode.EndCall();
|
|
|
|
if (encodeResult != Leopard_Success)
|
|
{
|
|
if (encodeResult == Leopard_TooMuchData)
|
|
{
|
|
cout << "Skipping this test: Parameters are unsupported by the codec" << endl;
|
|
return true;
|
|
}
|
|
cout << "Error: Leopard encode failed with result=" << encodeResult << ": " << leo_result_string(encodeResult) << endl;
|
|
LEO_DEBUG_BREAK;
|
|
return false;
|
|
}
|
|
|
|
// Lose random original data:
|
|
|
|
std::vector<uint16_t> original_losses(params.original_count);
|
|
ShuffleDeck16(prng, &original_losses[0], params.original_count);
|
|
|
|
for (unsigned i = 0, count = params.loss_count; i < count; ++i)
|
|
{
|
|
const unsigned loss_index = original_losses[i];
|
|
leopard::SIMDSafeFree(original_data[loss_index]);
|
|
original_data[loss_index] = nullptr;
|
|
}
|
|
|
|
// Lose random recovery data:
|
|
|
|
const unsigned recovery_loss_count = params.recovery_count - params.loss_count;
|
|
|
|
std::vector<uint16_t> recovery_losses(params.recovery_count);
|
|
ShuffleDeck16(prng, &recovery_losses[0], params.recovery_count);
|
|
|
|
for (unsigned i = 0, count = recovery_loss_count; i < count; ++i)
|
|
{
|
|
const unsigned loss_index = recovery_losses[i];
|
|
leopard::SIMDSafeFree(encode_work_data[loss_index]);
|
|
encode_work_data[loss_index] = nullptr;
|
|
}
|
|
|
|
// Decode:
|
|
|
|
t_leo_decode.BeginCall();
|
|
LeopardResult decodeResult = leo_decode(
|
|
params.buffer_bytes,
|
|
params.original_count,
|
|
params.recovery_count,
|
|
decode_work_count,
|
|
(void**)&original_data[0],
|
|
(void**)&encode_work_data[0],
|
|
(void**)&decode_work_data[0],
|
|
params.multithreaded ? LeopardFlags_Multithreaded : LeopardFlags_Defaults);
|
|
t_leo_decode.EndCall();
|
|
|
|
if (decodeResult != Leopard_Success)
|
|
{
|
|
cout << "Error: Leopard decode failed with result=" << decodeResult << ": " << leo_result_string(decodeResult) << endl;
|
|
LEO_DEBUG_BREAK;
|
|
return false;
|
|
}
|
|
|
|
for (unsigned i = 0; i < params.original_count; ++i)
|
|
{
|
|
if (!original_data[i])
|
|
{
|
|
if (!CheckPacket(decode_work_data[i], params.buffer_bytes))
|
|
{
|
|
cout << "Error: Data was corrupted" << endl;
|
|
LEO_DEBUG_BREAK;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free memory:
|
|
|
|
t_mem_free.BeginCall();
|
|
for (unsigned i = 0, count = params.original_count; i < count; ++i)
|
|
leopard::SIMDSafeFree(original_data[i]);
|
|
for (unsigned i = 0, count = encode_work_count; i < count; ++i)
|
|
leopard::SIMDSafeFree(encode_work_data[i]);
|
|
for (unsigned i = 0, count = decode_work_count; i < count; ++i)
|
|
leopard::SIMDSafeFree(decode_work_data[i]);
|
|
t_mem_free.EndCall();
|
|
}
|
|
|
|
#if 0
|
|
t_mem_alloc.Print(kTrials);
|
|
t_leo_encode.Print(kTrials);
|
|
t_leo_decode.Print(kTrials);
|
|
t_mem_free.Print(kTrials);
|
|
#endif
|
|
|
|
float encode_input_MBPS = total_bytes / (float)(t_leo_encode.MinCallUsec);
|
|
float encode_output_MBPS = params.buffer_bytes * (uint64_t)params.recovery_count / (float)(t_leo_encode.MinCallUsec);
|
|
float decode_input_MBPS = total_bytes / (float)(t_leo_decode.MinCallUsec);
|
|
float decode_output_MBPS = params.buffer_bytes * (uint64_t)params.loss_count / (float)(t_leo_decode.MinCallUsec);
|
|
|
|
cout << "Leopard Encoder(" << total_bytes / 1000000.f << " MB in " << params.original_count << " pieces, " << params.loss_count << " losses): Input=" << encode_input_MBPS << " MB/s, Output=" << encode_output_MBPS << " MB/s" << endl;
|
|
cout << "Leopard Decoder(" << total_bytes / 1000000.f << " MB in " << params.original_count << " pieces, " << params.loss_count << " losses): Input=" << decode_input_MBPS << " MB/s, Output=" << decode_output_MBPS << " MB/s" << endl << endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Entrypoint
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
SetCurrentThreadPriority();
|
|
|
|
FunctionTimer t_leo_init("leo_init");
|
|
|
|
t_leo_init.BeginCall();
|
|
if (0 != leo_init())
|
|
{
|
|
cout << "Failed to initialize" << endl;
|
|
return -1;
|
|
}
|
|
t_leo_init.EndCall();
|
|
t_leo_init.Print(1);
|
|
|
|
TestParameters params;
|
|
PCGRandom prng;
|
|
|
|
if (argc >= 2)
|
|
params.original_count = atoi(argv[1]);
|
|
if (argc >= 3)
|
|
params.recovery_count = atoi(argv[2]);
|
|
if (argc >= 4)
|
|
params.buffer_bytes = atoi(argv[3]);
|
|
if (argc >= 5)
|
|
params.loss_count = atoi(argv[4]);
|
|
if (argc >= 6)
|
|
params.multithreaded = (atoi(argv[5]) != 0);
|
|
|
|
if (params.loss_count > params.recovery_count)
|
|
params.loss_count = params.recovery_count;
|
|
|
|
cout << "Parameters: [original count=" << params.original_count << "] [recovery count=" << params.recovery_count << "] [buffer bytes=" << params.buffer_bytes << "] [loss count=" << params.loss_count << "] [random seed=" << params.seed << "]" << endl;
|
|
|
|
if (!Benchmark(params))
|
|
goto Failed;
|
|
|
|
#if 1
|
|
static const unsigned kMaxRandomData = 32768;
|
|
|
|
prng.Seed(params.seed, 8);
|
|
for (;; ++params.seed)
|
|
{
|
|
params.original_count = prng.Next() % kMaxRandomData;
|
|
params.recovery_count = prng.Next() % params.original_count + 1;
|
|
params.loss_count = prng.Next() % params.recovery_count + 1;
|
|
|
|
cout << "Parameters: [original count=" << params.original_count << "] [recovery count=" << params.recovery_count << "] [buffer bytes=" << params.buffer_bytes << "] [loss count=" << params.loss_count << "] [random seed=" << params.seed << "]" << endl;
|
|
|
|
if (!Benchmark(params))
|
|
goto Failed;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
for (unsigned original_count = 1; original_count <= 256; ++original_count)
|
|
{
|
|
for (unsigned recovery_count = 1; recovery_count <= original_count; ++recovery_count)
|
|
{
|
|
params.original_count = original_count;
|
|
params.recovery_count = recovery_count;
|
|
params.loss_count = recovery_count;
|
|
|
|
cout << "Parameters: [original count=" << params.original_count << "] [recovery count=" << params.recovery_count << "] [buffer bytes=" << params.buffer_bytes << "] [loss count=" << params.loss_count << "] [random seed=" << params.seed << "]" << endl;
|
|
|
|
if (!Benchmark(params))
|
|
goto Failed;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Failed:
|
|
getchar();
|
|
|
|
return 0;
|
|
}
|