Commit 7083377d authored by Paal Kvamme's avatar Paal Kvamme
Browse files

New unit test specifically for the two performance tweaks.

parent 2c3cba00
......@@ -18,6 +18,7 @@
#include "../iocontext.h"
#include "../exception.h"
#include "../impl/environment.h"
#include "../impl/mtguard.h"
#include <iostream>
#include <iomanip>
......@@ -35,6 +36,7 @@
using namespace OpenZGY;
using namespace OpenZGY::Formatters;
using Test_Utils::LocalFileAutoDelete;
using Test_Utils::CloudFileAutoDelete;
using Test_Utils::must_throw;
namespace {
......@@ -2340,6 +2342,231 @@ test_readwrite_cloud()
}
#endif
namespace {
template<typename T>
static std::string
formatMe(std::int64_t pos, std::int64_t ii, std::int64_t jj, std::int64_t kk, const T* value)
{
std::stringstream ss;
ss << "Pos " << pos << " brick ("
<< ii << "," << jj << "," << kk
<< ") value ("
<< (double)value[0] << "," << (double)value[1] << "," << (double)value[2]
<< ")";
return ss.str();
}
}
/**
* Test that reading a file BAT i.e. one brick at a time works.
* These now trigger some performance tweaks in the accessor.
*
* The test file has 4*5*6 = 120 bricks and is written in optimal order.
* most bricks store (i/64,j/64,k/64) in first three bytes to make it
* simple to check that the contents are correct. Every 7th brick will
* be missing. Every 13th brick will be filled with a constant value
* -(i+j+k)/64
*/
template<typename T, int bricksize, bool compression>
static void
do_testbat(const std::string& filename)
{
typedef IZgyWriter::size3i_t size3i_t;
const size3i_t bs{bricksize, bricksize, bricksize};
const size3i_t bsm1{bricksize-1, bricksize, bricksize};
const size3i_t size{4*bs[0]-10, 5*bs[1]-20, 6*bs[2]-23};
const SampleDataType dt = (sizeof(T) == 4 ? SampleDataType::float32 :
sizeof(T) == 2 ? SampleDataType::int16 :
SampleDataType::int8);
#ifdef HAVE_SD
SeismicStoreIOContext sd_context(*Test_Utils::default_sd_context());
sd_context.segsize(3); // Try to trigger read crossing seg boundary.
sd_context.segsplit(7);
const IOContext * const context = &sd_context;
#else
const IOContext * const context = nullptr;
#endif
ZgyWriterArgs args = ZgyWriterArgs()
.iocontext(context)
.size(size[0], size[1], size[2])
.bricksize(bs[0], bs[1], bs[2])
.datatype(dt)
.datarange(-1, +1)
.filename(filename);
if (compression)
args.zfp_compressor(99);
std::shared_ptr<OpenZGY::IZgyWriter> writer = IZgyWriter::open(args);
if (!TEST_CHECK(bool(writer)))
return;
std::vector<T> data(bs[0]*bs[1]*bs[2], 42);
std::int64_t pos{0};
for (std::int64_t ii=0; ii<size[0]; ii += bs[0]) {
for (std::int64_t jj=0; jj<size[1]; jj += bs[1]) {
for (std::int64_t kk=0; kk<size[2]; kk += bs[2]) {
if ((pos % 7) == 0) {
// missing brick
}
else if ((pos % 13) == 0) {
// Constant-value
data[0] = -(ii/bs[0]) - (jj/bs[1]) - (kk/bs[2]);
writer->writeconst(size3i_t{ii,jj,kk}, bs, data.data());
}
else {
data[0] = ii/bs[0];
data[1] = jj/bs[1];
data[2] = kk/bs[2];
writer->write(size3i_t{ii,jj,kk}, bs, data.data());
}
++pos;
}
}
}
writer->finalize(std::vector<OpenZGY::DecimationType>{
OpenZGY::DecimationType::Average
}, nullptr);
writer->close();
writer.reset();
// Test plan:
// Read one brick at a time using 10 threads. Can trigger both cases.
// Not tested: Second open is for read/write.
// Compression should trigger malloc hack, not the shortcut.
// Compressed reads might also cross segment boundary.
{
std::shared_ptr<OpenZGY::IZgyReader> reader =
IZgyReader::open(filename, context);
if (!TEST_CHECK(bool(reader)))
return;
if (verbose())
reader->filestats()->dump(std::cout, "");
InternalZGY::MTGuard guard;
#pragma omp parallel num_threads(10)
{
std::vector<T> check1(bs[0]*bs[1]*bs[2], 0);
std::vector<T> check2(bs[0]*bs[1]*bs[2], 0);
#pragma omp for schedule(dynamic,1)
for (int pos = 0; pos < 4*5*6; ++pos) {
int tmppos = pos;
const int kk = tmppos % 6; tmppos /= 6;
const int jj = tmppos % 5; tmppos /= 5;
const int ii = tmppos % 4;
std::fill(check1.begin(), check1.end(), -88);
std::fill(check2.begin(), check2.end(), -66);
guard.run([&](){
// Will trigger both tweaks, "brick shortcut" has precedence.
reader->read(size3i_t{ii*bs[0],jj*bs[1],kk*bs[2]}, bs, check1.data());
// Will trigger only the malloc tweak.
reader->read(size3i_t{ii*bs[0],jj*bs[1],kk*bs[2]}, bsm1, check2.data());
std::array<float,3> expect;
if ((pos % 7) == 0) {
expect = std::array<float,3>{0,0,0};
}
else if ((pos % 13) == 0) {
T value = -(ii+jj+kk);
expect = std::array<float,3>{value, value, value};
}
else {
expect = std::array<float,3>{ii, jj, kk};
}
if (check1[0]!=expect[0]||check1[1]!=expect[1] ||check1[2]!=expect[2]) {
std::string expect_str = formatMe(pos, ii, jj, kk, expect.data());
std::string actual_str = formatMe(pos, ii, jj, kk, check1.data());
TEST_EQUAL(actual_str, expect_str);
throw std::runtime_error("Mismatch in check 1");
}
if (check2[0]!=expect[0]||check2[1]!=expect[1] ||check2[2]!=expect[2]) {
std::string expect_str = formatMe(pos, ii, jj, kk, expect.data());
std::string actual_str = formatMe(pos, ii, jj, kk, check2.data());
TEST_EQUAL(actual_str, expect_str);
throw std::runtime_error("Mismatch in check 2");
}
// Too much hassle to check of the buffer if it is an edge brick.
// I want to check the others in case the read went across
// a segment boundary. That triggers soecuak case handling.
if (ii != 3 && jj != 4 && kk != 5 && (pos%7) != 0 && (pos%13) != 0) {
for (auto it = check1.begin() + 3; it != check1.end(); ++it)
if (*it != 42)
if (!TEST_EQUAL((double)*it, 42))
throw std::runtime_error("Mismatch in check 1 last part");
for (auto it = check2.begin() + 3; it != check2.end() - 1*bsm1[1]*bsm1[2]; ++it)
if (*it != 42)
if (!TEST_EQUAL((double)*it, 42))
throw std::runtime_error("Mismatch in check 2 last part");
}
});
} // loop
} // parallel region
try {
guard.finished();
}
catch (const std::exception& ex) {
TEST_EQUAL(std::string(ex.what()), std::string("success"));
}
}
}
static void
test_bat_local_1()
{
LocalFileAutoDelete lad("bat1.zgy");
do_testbat<std::int8_t, 32, false>(lad.name());
}
static void
test_bat_local_2()
{
LocalFileAutoDelete lad("bat2.zgy");
do_testbat<std::int16_t, 32, false>(lad.name());
}
static void
test_bat_local_4()
{
LocalFileAutoDelete lad("bat4.zgy");
do_testbat<float, 64, false>(lad.name());
}
static void
test_bat_local_zfp()
{
LocalFileAutoDelete lad("bat.zgy");
do_testbat<float, 64, true>(lad.name());
}
#ifdef HAVE_SD
static void
test_bat_sd_1()
{
CloudFileAutoDelete cad("bat1.zgy", Test_Utils::default_sd_context());
do_testbat<std::int8_t, 32, false>(cad.name());
}
static void
test_bat_sd_2()
{
CloudFileAutoDelete cad("bat2.zgy", Test_Utils::default_sd_context());
do_testbat<std::int16_t, 32, false>(cad.name());
}
static void
test_bat_sd_4()
{
CloudFileAutoDelete cad("bat4.zgy", Test_Utils::default_sd_context());
do_testbat<float, 64, false>(cad.name());
}
static void
test_bat_sd_zfp()
{
CloudFileAutoDelete cad("bat4.zgy", Test_Utils::default_sd_context());
do_testbat<float, 64, true>(cad.name());
}
#endif
class Register
{
public:
......@@ -2403,6 +2630,16 @@ public:
register_test("api.readwrite", test_readwrite_local);
#ifdef HAVE_SD
register_test("api.readwrite_cloud", test_readwrite_cloud);
#endif
register_test("api.bat_local_1", test_bat_local_1);
register_test("api.bat_local_2", test_bat_local_2);
register_test("api.bat_local_4", test_bat_local_4);
register_test("api.bat_local_zfp", test_bat_local_zfp);
#ifdef HAVE_SD
register_test("api.bat_sd_1", test_bat_sd_1);
register_test("api.bat_sd_2", test_bat_sd_2);
register_test("api.bat_sd_4", test_bat_sd_4);
register_test("api.bat_sd_zfp", test_bat_sd_zfp);
#endif
}
} dummy;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment