diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-12-17 17:36:16 +0100 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-12-17 17:36:16 +0100 |
| commit | ec9593e2ff0856d78b37df3a977a753be82bfddc (patch) | |
| tree | 684dad42207f48125db40ce0afb32046cdba1726 | |
| parent | ac3a91f4f173e7a38c70d5cd0caf5a025642312b (diff) | |
| download | nissy-core-ec9593e2ff0856d78b37df3a977a753be82bfddc.tar.gz nissy-core-ec9593e2ff0856d78b37df3a977a753be82bfddc.zip | |
Fix alignment
Diffstat (limited to '')
| -rw-r--r-- | benchmarks/benchmarks.md | 5 | ||||
| -rw-r--r-- | doc/h48.md | 12 | ||||
| -rw-r--r-- | doc/solvers.md | 9 | ||||
| -rw-r--r-- | shell/shell.c | 11 | ||||
| -rw-r--r-- | src/nissy.h | 12 | ||||
| -rw-r--r-- | tools/tool.h | 11 |
6 files changed, 53 insertions, 7 deletions
diff --git a/benchmarks/benchmarks.md b/benchmarks/benchmarks.md index adc3377..ddcb91f 100644 --- a/benchmarks/benchmarks.md +++ b/benchmarks/benchmarks.md | |||
| @@ -247,3 +247,8 @@ Time per cube adjusted for table size (in seconds \* GiB, lower is better). | |||
| 247 | * For H48, both GCC and Clang have been tried, with the same options; | 247 | * For H48, both GCC and Clang have been tried, with the same options; |
| 248 | the resulting executable was about 10% faster with GCC compared to Clang. | 248 | the resulting executable was about 10% faster with GCC compared to Clang. |
| 249 | vcube only supports compiling with Clang. | 249 | vcube only supports compiling with Clang. |
| 250 | * The performance of the H48 solver depends slightly, but measurably, on the | ||
| 251 | alignment of the pruning table in memory. This is not handled by the core | ||
| 252 | library, but by the program that uses it. For these tests, we have used as | ||
| 253 | reference implementation the program in `tools/301_solve_file`, which | ||
| 254 | ensures 64-byte alignment. | ||
| @@ -225,6 +225,8 @@ of 1, 2 or 3 can be used directly as a lower bound of b+1, b+2 and b+3 | |||
| 225 | respectively. However, a value of 0 could mean that the actual lower | 225 | respectively. However, a value of 0 could mean that the actual lower |
| 226 | bound is anything between 0 and b, so we cannot take b as a lower bound. | 226 | bound is anything between 0 and b, so we cannot take b as a lower bound. |
| 227 | 227 | ||
| 228 | #### Fallback tables | ||
| 229 | |||
| 228 | To be able to still use some sort of pruning value even when we get a | 230 | To be able to still use some sort of pruning value even when we get a |
| 229 | 0 read, we use a **fallback table**. Inspired by nxopt, this table is | 231 | 0 read, we use a **fallback table**. Inspired by nxopt, this table is |
| 230 | interleaved with the main table for cache efficiency: every 254 entries | 232 | interleaved with the main table for cache efficiency: every 254 entries |
| @@ -235,6 +237,16 @@ when looking up the minimum value in the fallback table after a 0 read. | |||
| 235 | Smaller lines (of 256 or 128 bits) have been tried, but they do not | 237 | Smaller lines (of 256 or 128 bits) have been tried, but they do not |
| 236 | give any significant improvement over 512 bit lines. | 238 | give any significant improvement over 512 bit lines. |
| 237 | 239 | ||
| 240 | This trick provides the gratest performance benefits if the main pruning | ||
| 241 | table is properly aligned. Unfortunately, as a design choice, the | ||
| 242 | solver is implemented here as a library that defer all memory allocation | ||
| 243 | business to the implementor. We do make sure that the table is properly | ||
| 244 | aligned in the programs provided in this repository (for example, the | ||
| 245 | rudimentary shell and the tools), but we do not enforce this in the | ||
| 246 | main library code. | ||
| 247 | |||
| 248 | #### Additional (fast) lookups | ||
| 249 | |||
| 238 | Moreover, as an additional heuristic, in case of a 0 read we also look | 250 | Moreover, as an additional heuristic, in case of a 0 read we also look |
| 239 | up another pruning value in a table that takes into account only the | 251 | up another pruning value in a table that takes into account only the |
| 240 | position of the edges. This table is small (around 1MB), so repeated | 252 | position of the edges. This table is small (around 1MB), so repeated |
diff --git a/doc/solvers.md b/doc/solvers.md index a246c54..837cec6 100644 --- a/doc/solvers.md +++ b/doc/solvers.md | |||
| @@ -16,6 +16,15 @@ about how this solver works, see [h48.md](./h48.md). For benchmarks see | |||
| 16 | * Moveset: HTM (all 18 basic moves). | 16 | * Moveset: HTM (all 18 basic moves). |
| 17 | * From 115MB to 59GB (roughly 2<sup>X</sup>*56MB). | 17 | * From 115MB to 59GB (roughly 2<sup>X</sup>*56MB). |
| 18 | 18 | ||
| 19 | *Note: for better performance, the solver's data should be 64-byte | ||
| 20 | aligned. To achieve this, one can use | ||
| 21 | [`aligned_alloc(64, size)`](https://en.cppreference.com/w/c/memory/aligned_alloc) | ||
| 22 | in C11 or later, | ||
| 23 | [`_aligned_malloc(size, 64)`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc) | ||
| 24 | on Windows platforms, or the | ||
| 25 | [aligned `new` operator](https://cppreference.com/w/cpp/memory/new/operator_new.html) | ||
| 26 | in C++17 or later.* | ||
| 27 | |||
| 19 | ## Coordinate solvers | 28 | ## Coordinate solvers |
| 20 | 29 | ||
| 21 | Various solvers to solve different substeps, commonly used for Fewest | 30 | Various solvers to solve different substeps, commonly used for Fewest |
diff --git a/shell/shell.c b/shell/shell.c index 0e624c9..698252d 100644 --- a/shell/shell.c +++ b/shell/shell.c | |||
| @@ -14,6 +14,13 @@ | |||
| 14 | #define SOLUTIONS_BUFFER_SIZE UINT64_C(500000) | 14 | #define SOLUTIONS_BUFFER_SIZE UINT64_C(500000) |
| 15 | #define MAX_PATH_LENGTH UINT64_C(10000) | 15 | #define MAX_PATH_LENGTH UINT64_C(10000) |
| 16 | 16 | ||
| 17 | #if defined(_WIN32) | ||
| 18 | #define wrap_aligned_alloc(align, size) malloc(size) | ||
| 19 | #else | ||
| 20 | #define wrap_aligned_alloc(align, size) \ | ||
| 21 | (((size) % (align) == 0) ? aligned_alloc((align), (size)) : malloc(size)) | ||
| 22 | #endif | ||
| 23 | |||
| 17 | #define FLAG_CUBE "-cube" | 24 | #define FLAG_CUBE "-cube" |
| 18 | #define FLAG_COMMAND "-command" | 25 | #define FLAG_COMMAND "-command" |
| 19 | #define FLAG_STR_CUBE "-cubestr" | 26 | #define FLAG_STR_CUBE "-cubestr" |
| @@ -348,7 +355,7 @@ gendata_exec(args_t *args) | |||
| 348 | return -2; | 355 | return -2; |
| 349 | } | 356 | } |
| 350 | 357 | ||
| 351 | buf = malloc(size); | 358 | buf = wrap_aligned_alloc((size_t)64, size); |
| 352 | 359 | ||
| 353 | ret = nissy_gendata(args->str_solver, size, buf); | 360 | ret = nissy_gendata(args->str_solver, size, buf); |
| 354 | if (ret < 0) { | 361 | if (ret < 0) { |
| @@ -448,7 +455,7 @@ solve_exec(args_t *args) | |||
| 448 | if (args->maxsolutions == 0) | 455 | if (args->maxsolutions == 0) |
| 449 | args->maxsolutions = args->optimal == 20 ? 1 : UINT_MAX; | 456 | args->maxsolutions = args->optimal == 20 ? 1 : UINT_MAX; |
| 450 | 457 | ||
| 451 | buf = malloc(size); | 458 | buf = wrap_aligned_alloc((size_t)64, size); |
| 452 | read = fread(buf, size, 1, file); | 459 | read = fread(buf, size, 1, file); |
| 453 | fclose(file); | 460 | fclose(file); |
| 454 | if (read != 1) { | 461 | if (read != 1) { |
diff --git a/src/nissy.h b/src/nissy.h index b5abc15..c85072a 100644 --- a/src/nissy.h +++ b/src/nissy.h | |||
| @@ -304,7 +304,8 @@ Parameters: | |||
| 304 | data_size - The size of the data buffer. It is advised to use | 304 | data_size - The size of the data buffer. It is advised to use |
| 305 | nissy_solverinfo to check how much memory is needed. | 305 | nissy_solverinfo to check how much memory is needed. |
| 306 | data - The return parameter for the generated data. | 306 | data - The return parameter for the generated data. |
| 307 | This buffer must have 8-byte alignment. | 307 | This buffer must have 8-byte alignment. Some solvers (such as |
| 308 | h48) will perform better if the buffer is 64-byte aligned. | ||
| 308 | 309 | ||
| 309 | Return values: | 310 | Return values: |
| 310 | NISSY_ERROR_INVALID_SOLVER - The given solver is not known. | 311 | NISSY_ERROR_INVALID_SOLVER - The given solver is not known. |
| @@ -328,7 +329,10 @@ Parameters: | |||
| 328 | solver - The name of the solver. | 329 | solver - The name of the solver. |
| 329 | data_size - The size of the data buffer. | 330 | data_size - The size of the data buffer. |
| 330 | data - The data for the solver. Can be computed with gendata. | 331 | data - The data for the solver. Can be computed with gendata. |
| 331 | This buffer must have 8-byte alignment. | 332 | This buffer must have 8-byte alignment. Some solvers (such as |
| 333 | h48) will perform better if the buffer is 64-byte aligned, but | ||
| 334 | this is not relevant for the purpose of checking the integrity | ||
| 335 | of the data. | ||
| 332 | 336 | ||
| 333 | Return values: | 337 | Return values: |
| 334 | NISSY_OK - The data is valid. | 338 | NISSY_OK - The data is valid. |
| @@ -358,7 +362,9 @@ Parameters: | |||
| 358 | to 0, the default value THREADS will be used. | 362 | to 0, the default value THREADS will be used. |
| 359 | data_size - The size of the data buffer. | 363 | data_size - The size of the data buffer. |
| 360 | data - The data for the solver. Can be computed with gendata. | 364 | data - The data for the solver. Can be computed with gendata. |
| 361 | This buffer must have 8-byte alignment. | 365 | This buffer must have 8-byte alignment. Some solvers |
| 366 | (such as h48) will perform better if the buffer is | ||
| 367 | 64-byte aligned. | ||
| 362 | sols_size - The size of the solutions buffer. | 368 | sols_size - The size of the solutions buffer. |
| 363 | sols - The return parameter for the solutions. The solutions are | 369 | sols - The return parameter for the solutions. The solutions are |
| 364 | separated by a '\n' (newline) and a '\0' (NULL character) | 370 | separated by a '\n' (newline) and a '\0' (NULL character) |
diff --git a/tools/tool.h b/tools/tool.h index c6fafbc..96e7f27 100644 --- a/tools/tool.h +++ b/tools/tool.h | |||
| @@ -8,6 +8,13 @@ | |||
| 8 | 8 | ||
| 9 | #include "../src/nissy.h" | 9 | #include "../src/nissy.h" |
| 10 | 10 | ||
| 11 | #if defined(_WIN32) | ||
| 12 | #define wrap_aligned_alloc(align, size) malloc(size) | ||
| 13 | #else | ||
| 14 | #define wrap_aligned_alloc(align, size) \ | ||
| 15 | (((size) % (align) == 0) ? aligned_alloc((align), (size)) : malloc(size)) | ||
| 16 | #endif | ||
| 17 | |||
| 11 | static void log_stderr(const char *, void *); | 18 | static void log_stderr(const char *, void *); |
| 12 | static double timerun(void (*)(void)); | 19 | static double timerun(void (*)(void)); |
| 13 | static void writetable(const unsigned char *, int64_t, const char *); | 20 | static void writetable(const unsigned char *, int64_t, const char *); |
| @@ -117,7 +124,7 @@ generatetable( | |||
| 117 | return -1; | 124 | return -1; |
| 118 | } | 125 | } |
| 119 | 126 | ||
| 120 | *buf = malloc(size); | 127 | *buf = wrap_aligned_alloc((size_t)64, size); |
| 121 | gensize = nissy_gendata(solver, size, *buf); | 128 | gensize = nissy_gendata(solver, size, *buf); |
| 122 | 129 | ||
| 123 | if (gensize != size) { | 130 | if (gensize != size) { |
| @@ -156,7 +163,7 @@ getdata( | |||
| 156 | } else { | 163 | } else { |
| 157 | printf("Reading tables from file %s\n", filename); | 164 | printf("Reading tables from file %s\n", filename); |
| 158 | size = nissy_solverinfo(solver, dataid); | 165 | size = nissy_solverinfo(solver, dataid); |
| 159 | *buf = malloc(size); | 166 | *buf = wrap_aligned_alloc((size_t)64, size); |
| 160 | sizeread = fread(*buf, size, 1, f); | 167 | sizeread = fread(*buf, size, 1, f); |
| 161 | fclose(f); | 168 | fclose(f); |
| 162 | if (sizeread != 1) { | 169 | if (sizeread != 1) { |
