SDDS ToolKit Programs and Libraries for C and Python
Loading...
Searching...
No Matches
benchmark_sdds3.cc
Go to the documentation of this file.
1/**
2 * @file benchmark_sdds3.cc
3 * @brief Comparable serial C and C++ SDDS benchmark workloads.
4 *
5 * @details Measures full and selective reads, page seeks, writes, append and
6 * update operations, strings, arrays, and compressed streams using equivalent
7 * C and C++ workloads.
8 *
9 * @copyright
10 * - (c) 2026 The University of Chicago
11 *
12 * @license
13 * This file is distributed under the terms of the Software License Agreement
14 * found in the file LICENSE included with this distribution.
15 */
16
17#include "SDDS.hpp"
18#include "SDDS.h"
19
20#include <chrono>
21#include <cmath>
22#include <cstdint>
23#include <filesystem>
24#include <iostream>
25#include <string>
26#include <vector>
27
28#if defined(_WIN32)
29# if !defined(NOMINMAX)
30# define NOMINMAX
31# endif
32# include <windows.h>
33# include <psapi.h>
34#else
35# include <sys/resource.h>
36#endif
37
38namespace {
39
40constexpr std::int32_t columns = 20;
41constexpr std::int32_t pages = 4;
42
43std::vector<char> mutablePath(const std::filesystem::path &path) {
44 const std::string text = path.string();
45 std::vector<char> result(text.begin(), text.end());
46 result.push_back('\0');
47 return result;
48}
49
50std::uint64_t peakMemoryKib() {
51#if defined(_WIN32)
52 PROCESS_MEMORY_COUNTERS counters{};
53 if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) return 0;
54 return static_cast<std::uint64_t>(counters.PeakWorkingSetSize / 1024U);
55#else
56 struct rusage usage{};
57 if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
58# if defined(__APPLE__)
59 return static_cast<std::uint64_t>(usage.ru_maxrss / 1024);
60# else
61 return static_cast<std::uint64_t>(usage.ru_maxrss);
62# endif
63#endif
64}
65
66sdds::Layout numericLayout() {
67 sdds::DataOptions data;
68 data.mode = sdds::DataMode::Binary;
69 data.majorOrder = sdds::MajorOrder::Row;
70 data.byteOrder = sdds::ByteOrder::Little;
71 sdds::LayoutBuilder builder;
72 builder.setDataOptions(data);
73 for (std::int32_t column = 0; column < columns; ++column)
74 builder.addColumn({{"c" + std::to_string(column), {}, {}, {}, {}, sdds::Type::Double}, 0});
75 return builder.build();
76}
77
78sdds::Layout mixedLayout() {
79 sdds::LayoutBuilder builder;
80 builder.addArray({{"array", {}, {}, {}, {}, sdds::Type::Double}, 0, 1, {}})
81 .addColumn({{"c0", {}, {}, {}, {}, sdds::Type::Double}, 0})
82 .addColumn({{"text", {}, {}, {}, {}, sdds::Type::String}, 0});
83 return builder.build();
84}
85
86std::vector<double> columnValues(std::int64_t rows, std::int32_t column,
87 std::int32_t page) {
88 std::vector<double> values(static_cast<std::size_t>(rows));
89 for (std::int64_t row = 0; row < rows; ++row)
90 values[static_cast<std::size_t>(row)] = page * 1000.0 + column + row * 0.001;
91 return values;
92}
93
94void generateCpp(const std::filesystem::path &path, std::int64_t rows,
95 sdds::Compression compression = sdds::Compression::None) {
96 auto layout = numericLayout();
97 sdds::WriterOptions options;
98 options.compression = compression;
99 options.gzipLevel = 1;
100 options.lzmaPreset = 1;
101 auto writer = sdds::Writer::create(path, layout, options);
102 for (std::int32_t page = 0; page < pages; ++page) {
103 writer.beginPage(rows);
104 for (std::int32_t column = 0; column < columns; ++column)
105 writer.setColumn("c" + std::to_string(column), columnValues(rows, column, page));
106 writer.commitPage();
107 }
108 writer.close();
109}
110
111void generateMixed(const std::filesystem::path &path, std::int64_t rows) {
112 const auto layout = mixedLayout();
113 auto writer = sdds::Writer::create(path, layout);
114 std::vector<double> array(10000, 1.25);
115 std::vector<double> numeric(static_cast<std::size_t>(rows), 2.5);
116 std::vector<std::string> strings(static_cast<std::size_t>(rows));
117 for (std::int64_t row = 0; row < rows; ++row)
118 strings[static_cast<std::size_t>(row)] = "row-" + std::to_string(row % 1000);
119 for (std::int32_t page = 0; page < pages; ++page) {
120 writer.beginPage(rows);
121 writer.setArray("array", {{static_cast<std::int32_t>(array.size())}, array});
122 writer.setColumn("c0", numeric);
123 writer.setColumn("text", strings);
124 writer.commitPage();
125 }
126 writer.close();
127}
128
129double cppRead(const std::filesystem::path &path, std::string_view mode) {
130 auto reader = sdds::Reader::open(path);
131 sdds::ReadRequest request;
132 if (mode == "project") {
133 request.parameters = sdds::FieldSelection::noFields();
134 request.arrays = sdds::FieldSelection::noFields();
135 request.columns = sdds::FieldSelection::only({"c0", "c1"});
136 } else if (mode == "sparse") {
137 request.rows.stride = 10;
138 }
139 double checksum = 0;
140 while (auto page = reader.next(request)) {
141 const auto &values = page->columnAs<double>("c0");
142 for (const double value : values) checksum += value;
143 if (mode == "project")
144 for (const double value : page->columnAs<double>("c1")) checksum += value;
145 }
146 reader.close();
147 return checksum;
148}
149
150double cppSeek(const std::filesystem::path &path) {
151 auto reader = sdds::Reader::open(path);
152 reader.buildPageIndex();
153 sdds::ReadRequest request;
154 request.parameters = sdds::FieldSelection::noFields();
155 request.arrays = sdds::FieldSelection::noFields();
156 request.columns = sdds::FieldSelection::only({"c0"});
157 request.rows.first = 0;
158 request.rows.count = 1;
159 double checksum = 0;
160 for (std::int32_t iteration = 0; iteration < 20; ++iteration) {
161 reader.gotoPage(iteration % 2 ? pages : 1);
162 checksum += reader.next(request)->columnAs<double>("c0")[0];
163 }
164 reader.close();
165 return checksum;
166}
167
168double cppMixedRead(const std::filesystem::path &path, bool strings) {
169 auto reader = sdds::Reader::open(path);
170 sdds::ReadRequest request;
171 request.parameters = sdds::FieldSelection::noFields();
172 request.arrays = strings ? sdds::FieldSelection::noFields()
173 : sdds::FieldSelection::only({"array"});
174 request.columns = strings ? sdds::FieldSelection::only({"text"})
175 : sdds::FieldSelection::noFields();
176 double checksum = 0;
177 while (auto page = reader.next(request)) {
178 if (strings)
179 for (const auto &value : page->columnAs<std::string>("text")) checksum += value.size();
180 else
181 for (const double value : page->arrayAs<double>("array")) checksum += value;
182 }
183 reader.close();
184 return checksum;
185}
186
187double cRead(const std::filesystem::path &path, std::string_view mode) {
188 SDDS_DATASET dataset{};
189 auto filename = mutablePath(path);
190 if (!SDDS_InitializeInput(&dataset, filename.data())) return NAN;
191 if (mode == "project") {
192 char first[] = "c0";
193 char second[] = "c1";
194 char *names[] = {first, second};
195 SDDS_SetColumnFlags(&dataset, 0);
196 if (!SDDS_SetColumnsOfInterest(&dataset, SDDS_NAME_ARRAY, 2, names)) return NAN;
197 }
198 double checksum = 0;
199 std::int32_t page = 0;
200 while ((page = mode == "sparse" ? SDDS_ReadPageSparse(&dataset, 0, 10, 0, 0)
201 : SDDS_ReadPage(&dataset)) > 0) {
202 double *values = SDDS_GetColumnInDoubles(&dataset, const_cast<char *>("c0"));
203 const std::int64_t rows = SDDS_CountRowsOfInterest(&dataset);
204 for (std::int64_t row = 0; row < rows; ++row) checksum += values[row];
205 SDDS_Free(values);
206 if (mode == "project") {
207 values = SDDS_GetColumnInDoubles(&dataset, const_cast<char *>("c1"));
208 for (std::int64_t row = 0; row < rows; ++row) checksum += values[row];
209 SDDS_Free(values);
210 }
211 }
212 SDDS_Terminate(&dataset);
213 return checksum;
214}
215
216double cSeek(const std::filesystem::path &path) {
217 SDDS_DATASET dataset{};
218 auto filename = mutablePath(path);
220 if (!SDDS_InitializeInput(&dataset, filename.data())) {
221 SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
222 return NAN;
223 }
224 double checksum = 0;
225 while (SDDS_ReadPageSparse(&dataset, 0, 1000000000, 0, 0) > 0) {}
226 for (std::int32_t iteration = 0; iteration < 20; ++iteration) {
227 if (!SDDS_GotoPage(&dataset, iteration % 2 ? pages : 1)) {
228 SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
229 return NAN;
230 }
231 if (SDDS_ReadPageSparse(&dataset, 0, 1000000000, 0, 0) <= 0) {
232 SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
233 return NAN;
234 }
235 double *values = SDDS_GetColumnInDoubles(&dataset, const_cast<char *>("c0"));
236 checksum += values[0];
237 SDDS_Free(values);
238 }
239 SDDS_Terminate(&dataset);
240 return checksum;
241}
242
243double cMixedRead(const std::filesystem::path &path, bool strings) {
244 SDDS_DATASET dataset{};
245 auto filename = mutablePath(path);
246 if (!SDDS_InitializeInput(&dataset, filename.data())) return NAN;
247 if (!SDDS_SetColumnFlags(&dataset, 0)) return NAN;
248 if (strings) {
249 char text[] = "text";
250 char *names[] = {text};
251 if (!SDDS_SetColumnsOfInterest(&dataset, SDDS_NAME_ARRAY, 1, names)) return NAN;
252 }
253 double checksum = 0;
254 while (SDDS_ReadPage(&dataset) > 0) {
255 if (strings) {
256 char **values = SDDS_GetColumnInString(&dataset, const_cast<char *>("text"));
257 const std::int64_t rows = SDDS_CountRowsOfInterest(&dataset);
258 for (std::int64_t row = 0; row < rows; ++row)
259 checksum += std::string(values[row]).size();
260 SDDS_FreeStringArray(values, rows);
261 SDDS_Free(values);
262 } else {
263 SDDS_ARRAY *array = SDDS_GetArray(&dataset, const_cast<char *>("array"), nullptr);
264 if (!array) return NAN;
265 const auto *values = static_cast<const double *>(array->data);
266 for (std::int32_t index = 0; index < array->elements; ++index) checksum += values[index];
267 SDDS_FreeArray(array);
268 }
269 }
270 SDDS_Terminate(&dataset);
271 return checksum;
272}
273
274double cppWrite(const std::filesystem::path &path, std::int64_t rows) {
275 generateCpp(path, rows);
276 return static_cast<double>(std::filesystem::file_size(path));
277}
278
279double cWrite(const std::filesystem::path &path, std::int64_t rows) {
280 SDDS_DATASET dataset{};
281 auto filename = mutablePath(path);
282 if (!SDDS_InitializeOutput(&dataset, SDDS_BINARY, 0, nullptr, nullptr, filename.data()))
283 return NAN;
284 for (std::int32_t column = 0; column < columns; ++column) {
285 const std::string name = "c" + std::to_string(column);
286 if (!SDDS_DefineSimpleColumn(&dataset, name.c_str(), nullptr, SDDS_DOUBLE)) return NAN;
287 }
288 if (!SDDS_WriteLayout(&dataset)) return NAN;
289 for (std::int32_t page = 0; page < pages; ++page) {
290 if (!SDDS_StartPage(&dataset, rows)) return NAN;
291 for (std::int32_t column = 0; column < columns; ++column) {
292 const std::string name = "c" + std::to_string(column);
293 auto values = columnValues(rows, column, page);
294 if (!SDDS_SetColumn(&dataset, SDDS_SET_BY_NAME, values.data(), rows, name.c_str()))
295 return NAN;
296 }
297 if (!SDDS_WritePage(&dataset)) return NAN;
298 }
299 if (!SDDS_Terminate(&dataset)) return NAN;
300 return static_cast<double>(std::filesystem::file_size(path));
301}
302
303double cppAppend(const std::filesystem::path &path) {
304 auto writer = sdds::Writer::append(path);
305 writer.beginPage(1);
306 for (std::int32_t column = 0; column < columns; ++column)
307 writer.setColumn("c" + std::to_string(column), std::vector<double>{column * 1.0});
308 writer.commitPage();
309 writer.close();
310 return static_cast<double>(std::filesystem::file_size(path));
311}
312
313double cAppend(const std::filesystem::path &path) {
314 SDDS_DATASET dataset{};
315 if (!SDDS_InitializeAppend(&dataset, path.string().c_str())) return NAN;
316 if (!SDDS_StartPage(&dataset, 1)) return NAN;
317 for (std::int32_t column = 0; column < columns; ++column) {
318 const std::string name = "c" + std::to_string(column);
319 double value = column;
320 if (!SDDS_SetColumn(&dataset, SDDS_SET_BY_NAME, &value, 1, name.c_str())) return NAN;
321 }
322 if (!SDDS_WritePage(&dataset) || !SDDS_Terminate(&dataset)) return NAN;
323 return static_cast<double>(std::filesystem::file_size(path));
324}
325
326double cppUpdate(const std::filesystem::path &path) {
327 auto writer = sdds::Writer::appendToLastPage(path, 1);
328 const std::int64_t firstRow = writer.rowsPresent();
329 for (std::int32_t column = 0; column < columns; ++column)
330 writer.setColumn("c" + std::to_string(column), std::vector<double>{column * 1.0}, firstRow);
331 writer.updatePage(true);
332 writer.close();
333 return static_cast<double>(std::filesystem::file_size(path));
334}
335
336double cUpdate(const std::filesystem::path &path) {
337 SDDS_DATASET dataset{};
338 std::int64_t rowsPresent = 0;
339 if (!SDDS_InitializeAppendToPage(&dataset, path.string().c_str(), 1, &rowsPresent)) return NAN;
340 if (!SDDS_LengthenTable(&dataset, 1)) return NAN;
341 for (std::int32_t column = 0; column < columns; ++column) {
342 const std::string name = "c" + std::to_string(column);
343 if (!SDDS_SetRowValues(&dataset, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, rowsPresent,
344 name.c_str(), static_cast<double>(column), nullptr))
345 return NAN;
346 }
347 if (!SDDS_UpdatePage(&dataset, FLUSH_TABLE) || !SDDS_Terminate(&dataset)) return NAN;
348 return static_cast<double>(std::filesystem::file_size(path));
349}
350
351void result(const std::string &engine, const std::string &mode, double seconds,
352 double checksum) {
353 std::cout << "{\"engine\":\"" << engine << "\",\"mode\":\"" << mode
354 << "\",\"seconds\":" << seconds << ",\"peak_kib\":" << peakMemoryKib()
355 << ",\"checksum\":" << checksum << "}\n";
356}
357
358} // namespace
359
360int main(int argc, char **argv) {
361 if (argc < 3) {
362 std::cerr << "usage: benchmark_sddspp MODE PATH [ROWS]\n";
363 return 2;
364 }
365 const std::string mode(argv[1]);
366 const std::filesystem::path path(argv[2]);
367 const std::int64_t rows = argc > 3 ? std::stoll(argv[3]) : 100000;
368 if (!path.parent_path().empty()) std::filesystem::create_directories(path.parent_path());
369 if (mode == "generate") {
370 generateCpp(path, rows);
371 return 0;
372 }
373 if (mode == "generate-gzip") {
374 generateCpp(path, rows, sdds::Compression::Gzip);
375 return 0;
376 }
377 if (mode == "generate-xz") {
378 generateCpp(path, rows, sdds::Compression::Xz);
379 return 0;
380 }
381 if (mode == "generate-mixed") {
382 generateMixed(path, rows);
383 return 0;
384 }
385 const auto start = std::chrono::steady_clock::now();
386 double checksum = NAN;
387 std::string engine;
388 std::string workload;
389 if (mode.rfind("cpp-", 0) == 0) {
390 engine = "cpp";
391 workload = mode.substr(4);
392 if (workload == "full" || workload == "project" || workload == "sparse" ||
393 workload == "compression")
394 checksum = cppRead(path, workload == "compression" ? "full" : workload);
395 else if (workload == "strings" || workload == "arrays")
396 checksum = cppMixedRead(path, workload == "strings");
397 else if (workload == "seek") checksum = cppSeek(path);
398 else if (workload == "write") checksum = cppWrite(path, rows);
399 else if (workload == "append") checksum = cppAppend(path);
400 else if (workload == "update") checksum = cppUpdate(path);
401 } else if (mode.rfind("c-", 0) == 0) {
402 engine = "c";
403 workload = mode.substr(2);
404 if (workload == "full" || workload == "project" || workload == "sparse" ||
405 workload == "compression")
406 checksum = cRead(path, workload == "compression" ? "full" : workload);
407 else if (workload == "strings" || workload == "arrays")
408 checksum = cMixedRead(path, workload == "strings");
409 else if (workload == "seek") checksum = cSeek(path);
410 else if (workload == "write") checksum = cWrite(path, rows);
411 else if (workload == "append") checksum = cAppend(path);
412 else if (workload == "update") checksum = cUpdate(path);
413 }
414 if (engine.empty() || std::isnan(checksum)) return 2;
415 const double seconds = std::chrono::duration<double>(
416 std::chrono::steady_clock::now() - start).count();
417 result(engine, workload, seconds, checksum);
418 return 0;
419}
SDDS (Self Describing Data Set) Data Types Definitions and Function Prototypes.
int32_t SDDS_SetDefaultIOBufferSize(int32_t newValue)
Definition SDDS_binary.c:82
int32_t SDDS_LengthenTable(SDDS_DATASET *SDDS_dataset, int64_t n_additional_rows)
int32_t SDDS_SetRowValues(SDDS_DATASET *SDDS_dataset, int32_t mode, int64_t row,...)
int32_t SDDS_StartPage(SDDS_DATASET *SDDS_dataset, int64_t expected_n_rows)
int32_t SDDS_SetColumn(SDDS_DATASET *SDDS_dataset, int32_t mode, void *data, int64_t rows,...)
Sets the values for one data column in the current data table of an SDDS dataset.
int64_t SDDS_CountRowsOfInterest(SDDS_DATASET *SDDS_dataset)
Counts the number of rows marked as "of interest" in the current data table.
int32_t SDDS_SetColumnsOfInterest(SDDS_DATASET *SDDS_dataset, int32_t mode,...)
Sets the acceptance flags for columns based on specified naming criteria.
SDDS_ARRAY * SDDS_GetArray(SDDS_DATASET *SDDS_dataset, char *array_name, SDDS_ARRAY *memory)
Retrieves an array from the current data table of an SDDS dataset.
char ** SDDS_GetColumnInString(SDDS_DATASET *SDDS_dataset, char *column_name)
Retrieves the data of a specified column as an array of strings, considering only rows marked as "of ...
int32_t SDDS_SetColumnFlags(SDDS_DATASET *SDDS_dataset, int32_t column_flag_value)
Sets the acceptance flags for all columns in the current data table of a data set.
double * SDDS_GetColumnInDoubles(SDDS_DATASET *SDDS_dataset, char *column_name)
Retrieves the data of a specified numerical column as an array of doubles, considering only rows mark...
int32_t SDDS_ReadPageSparse(SDDS_DATASET *SDDS_dataset, uint32_t mode, int64_t sparse_interval, int64_t sparse_offset, int32_t sparse_statistics)
int32_t SDDS_InitializeInput(SDDS_DATASET *SDDS_dataset, char *filename)
Definition SDDS_input.c:50
int32_t SDDS_Terminate(SDDS_DATASET *SDDS_dataset)
int32_t SDDS_ReadPage(SDDS_DATASET *SDDS_dataset)
int32_t SDDS_GotoPage(SDDS_DATASET *SDDS_dataset, int32_t page_number)
Sets the current page of the SDDS dataset to the specified page number.
int32_t SDDS_InitializeAppend(SDDS_DATASET *SDDS_dataset, const char *filename)
Initializes the SDDS dataset for appending data by adding a new page to an existing file.
int32_t SDDS_InitializeOutput(SDDS_DATASET *SDDS_dataset, int32_t data_mode, int32_t lines_per_row, const char *description, const char *contents, const char *filename)
Initializes the SDDS output dataset.
int32_t SDDS_InitializeAppendToPage(SDDS_DATASET *SDDS_dataset, const char *filename, int64_t updateInterval, int64_t *rowsPresentReturn)
Initializes the SDDS dataset for appending data to the last page of an existing file.
int32_t SDDS_DefineSimpleColumn(SDDS_DATASET *SDDS_dataset, const char *name, const char *unit, int32_t type)
Defines a simple data column within the SDDS dataset.
int32_t SDDS_UpdatePage(SDDS_DATASET *SDDS_dataset, uint32_t mode)
Updates the current page of the SDDS dataset.
int32_t SDDS_WritePage(SDDS_DATASET *SDDS_dataset)
Writes the current data table to the output file.
int32_t SDDS_WriteLayout(SDDS_DATASET *SDDS_dataset)
Writes the SDDS layout header to the output file.
void SDDS_FreeArray(SDDS_ARRAY *array)
Frees memory allocated for an SDDS array structure.
int32_t SDDS_FreeStringArray(char **string, int64_t strings)
Frees an array of strings by deallocating each individual string.
void SDDS_PrintErrors(FILE *fp, int32_t mode)
Prints recorded error messages to a specified file stream.
Definition SDDS_utils.c:474
void SDDS_Free(void *mem)
Free memory previously allocated by SDDS_Malloc.
Definition SDDS_utils.c:721
#define SDDS_DOUBLE
Identifier for the double data type.
Definition SDDStypes.h:37