snapraid/raid/memory.c

235 lines
7.6 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-or-later
2026-04-02 16:25:49 +02:00
// Copyright (C) 2013 Andrea Mazzoleni
2013-12-29 22:13:08 +01:00
#include "internal.h"
#include "memory.h"
2016-05-15 14:18:44 +02:00
void *raid_malloc_align(size_t size, size_t align_size, void **freeptr)
2013-12-29 22:13:08 +01:00
{
unsigned char *ptr;
uintptr_t offset;
2016-05-15 14:18:44 +02:00
ptr = malloc(size + align_size);
2015-03-18 19:19:32 +01:00
if (!ptr) {
/* LCOV_EXCL_START */
2013-12-29 22:13:08 +01:00
return 0;
2015-03-18 19:19:32 +01:00
/* LCOV_EXCL_STOP */
}
2013-12-29 22:13:08 +01:00
*freeptr = ptr;
2016-05-15 14:18:44 +02:00
offset = ((uintptr_t)ptr) % align_size;
2013-12-29 22:13:08 +01:00
if (offset != 0)
2016-05-15 14:18:44 +02:00
ptr += align_size - offset;
2013-12-29 22:13:08 +01:00
return ptr;
}
2016-05-15 14:18:44 +02:00
void *raid_malloc(size_t size, void **freeptr)
{
return raid_malloc_align(size, RAID_MALLOC_ALIGN, freeptr);
2016-05-15 14:18:44 +02:00
}
unsigned raid_optimal_displacement(int n)
{
if (n <= 8)
return 24 * 64;
if (n <= 16)
return 28 * 64;
if (n <= 32)
return 30 * 64;
return 33 * 64;
}
/*
* The 4096 bytes represents a full 64-set * 64-byte L1 cache cycle.
*/
#define RAID_WRAP_SIZE 4096
/*
* PREFETCHER MITIGATION VIA PAGE-OFFSET RANDOMIZATION (WAY-SHIFTING)
*
* STRIDE_NOISE is a sequence of small, non-linear multipliers used to inject
* variable byte offsets into the physical distance between consecutive disk
* buffers during allocation. These multipliers are applied in 4096-byte chunks.
*
* The parity generation loops contain a massive cross-disk jump in the inner
* loop. If the distance between every disk is a constant delta (e.g., exactly
* 256KB + fixed displacement bytes), modern IP-based stride prefetchers
* (particularly on architectures like AMD Zen 5) will easily recognize the pattern.
*
* Because the prefetcher is biased to aggressively accelerate constant positive
* strides, it incorrectly assumes the program is executing a massive linear
* sweep of memory. It begins flooding the memory controller with speculative,
* useless requests for data far beyond the active disks. This "garbage traffic"
* saturates the memory bus, starving the AVX/SIMD execution engine of
* the actual data it needs and crippling throughput.
*
* We must blind the prefetcher by making the stride unpredictable, but we CANNOT
* alter the carefully calculated L1 cache set mapping (raid_optimal_displacement),
* otherwise we will cause catastrophic L1 cache thrashing.
*
* - The standard x86 L1 data cache has 64 sets, and a cache line is 64 bytes.
* A full wrap-around of the L1 cache is exactly 64 * 64 = 4096 bytes (which
* also perfectly aligns with a standard 4KB memory page).
*
* - By adding a random multiple of 4096 bytes to our jump:
* 1. The physical memory address changes wildly, breaking the constant stride.
* 2. The modulo-64 cache math is completely unaffected ((X + 4096) % 64 == X % 64).
*
* The IP-based stride prefetcher monitors the cross-disk jumps and sees a
* chaotic sequence of massive, fluctuating deltas (e.g., +275KB, +267KB, +288KB).
* Because the stride is never constant, the prefetcher's internal confidence
* counter never builds up. The prefetcher safely disables itself for the inner
* loop.
*
* With the noisy prefetcher silenced, the memory bus is cleared. The CPU's L2
* stream prefetcher is left alone to perfectly track the independent +64 byte
* forward reads for each individual disk. The AVX/SIMD execution engine receives
* 100% of the bandwidth, yielding maximum physical throughput.
*
* These are the results in MB/s with no stride noise on a Zen 5 CPU:
*
* RAID functions used for computing the parity with 'sync':
* best int8 int32 int64 sse2 sse2e ssse3 ssse3e avx2 avx2e avx512 gfni gfni512
* gen1 avx512 48947 75608 26465 70482 73848
* gen2 avx512 9989 19702 28144 34140 42712 22374 30712 68590
* genz avx2e 5935 11653 17763 19398 38789
* gen3 avx2e 2214 20620 23859 33947 24109 20045 31187
* gen4 avx2e 1622 13843 17071 28194 25679 18826 20780
* gen5 avx2e 1360 11775 12980 20285 20377 17509 16149
* gen6 avx2e 1133 8478 11189 19951 16831 18965 13912
*
* These are the results in MB/s with stride noise on a Zen 5 CPU:
*
* RAID functions used for computing the parity with 'sync':
* best int8 int32 int64 sse2 sse2e ssse3 ssse3e avx2 avx2e avx512 gfni gfni512
* gen1 avx512 48161 88091 110896 122684 121917
* gen2 avx512 9970 19776 43223 45989 86221 68418 110524 112067
* genz avx2e 5953 11730 18598 19475 39411
* gen3 avx2e 2219 21784 24347 49416 43295 95555 100458
* gen4 avx2e 1624 14216 17408 38422 29275 73658 84268
* gen5 avx2e 1361 12369 13585 28328 22052 63672 68036
* gen6 avx2e 1131 8399 11692 23918 17703 53620 56806
*/
static const unsigned STRIDE_NOISE[16] = {
0, 3, 1, 6, 2, 5, 7, 4,
1, 4, 0, 7, 3, 6, 2, 5
};
void **raid_malloc_vector_align(int n, size_t size, size_t align_size, ssize_t displacement_size, ssize_t wrap_size, void **freeptr)
2013-12-29 22:13:08 +01:00
{
void **v;
unsigned char *va;
2014-01-02 17:51:50 +01:00
int i;
2013-12-29 22:13:08 +01:00
BUG_ON(n <= 0);
2014-01-02 17:51:50 +01:00
v = malloc(n * sizeof(void *));
2015-03-18 19:19:32 +01:00
if (!v) {
/* LCOV_EXCL_START */
2013-12-29 22:13:08 +01:00
return 0;
2015-03-18 19:19:32 +01:00
/* LCOV_EXCL_STOP */
}
2013-12-29 22:13:08 +01:00
/*
* The allocated buffer must safely hold the disk chunks, the L1 fixed displacement,
* and the variable STRIDE_NOISE. Because the maximum noise multiplier in the array
* is 7, reserving 8 * RAID_WRAP_SIZE per disk guarantees the pointer will never overflow
* the allocated memory block.
*/
va = raid_malloc_align(n * (size + displacement_size + 8 * wrap_size), align_size, freeptr);
2013-12-29 22:13:08 +01:00
if (!va) {
2015-03-18 19:19:32 +01:00
/* LCOV_EXCL_START */
2013-12-29 22:13:08 +01:00
free(v);
return 0;
2015-03-18 19:19:32 +01:00
/* LCOV_EXCL_STOP */
2013-12-29 22:13:08 +01:00
}
2014-01-02 17:51:50 +01:00
for (i = 0; i < n; ++i) {
v[i] = va;
/* move past the active disk buffer */
va += size;
/* apply the optimal L1 cache spacing */
va += displacement_size;
/* inject the variable noise multiplier to blind the stride prefetcher */
va += STRIDE_NOISE[i % 16] * wrap_size;
2014-01-02 17:51:50 +01:00
}
2013-12-29 22:13:08 +01:00
return v;
}
void **raid_malloc_vector(int n, size_t size, void **freeptr)
2016-05-15 14:18:44 +02:00
{
return raid_malloc_vector_align(n, size, RAID_MALLOC_ALIGN, raid_optimal_displacement(n), RAID_WRAP_SIZE, freeptr);
2016-05-15 14:18:44 +02:00
}
void raid_mrand_vector(unsigned seed, int n, size_t size, void **vv)
2014-01-02 17:51:50 +01:00
{
2015-03-18 08:21:28 +01:00
unsigned char **v = (unsigned char **)vv;
2014-01-02 17:51:50 +01:00
int i;
size_t j;
for (i = 0; i < n; ++i)
for (j = 0; j < size; ++j) {
/* basic C99/C11 linear congruential generator */
seed = seed * 1103515245U + 12345U;
v[i][j] = seed >> 16;
}
2014-01-02 17:51:50 +01:00
}
int raid_mtest_vector(int n, size_t size, void **vv)
2013-12-29 22:13:08 +01:00
{
2015-03-18 08:21:28 +01:00
unsigned char **v = (unsigned char **)vv;
2014-01-02 17:51:50 +01:00
int i;
2013-12-29 22:13:08 +01:00
size_t j;
unsigned k;
unsigned char d;
unsigned char p;
/* fill with 0 */
d = 0;
2014-01-02 17:51:50 +01:00
for (i = 0; i < n; ++i)
2013-12-29 22:13:08 +01:00
for (j = 0; j < size; ++j)
v[i][j] = d;
/* test with all the byte patterns */
for (k = 1; k < 256; ++k) {
p = d;
d = k;
/* forward fill */
2014-01-02 17:51:50 +01:00
for (i = 0; i < n; ++i) {
2013-12-29 22:13:08 +01:00
for (j = 0; j < size; ++j) {
2015-03-18 19:19:32 +01:00
if (v[i][j] != p) {
/* LCOV_EXCL_START */
2013-12-29 22:13:08 +01:00
return -1;
2015-03-18 19:19:32 +01:00
/* LCOV_EXCL_STOP */
}
2013-12-29 22:13:08 +01:00
v[i][j] = d;
}
}
p = d;
d = ~p;
/* backward fill with complement */
2014-01-02 17:51:50 +01:00
for (i = 0; i < n; ++i) {
2013-12-29 22:13:08 +01:00
for (j = size; j > 0; --j) {
2015-03-18 19:19:32 +01:00
if (v[i][j - 1] != p) {
/* LCOV_EXCL_START */
2013-12-29 22:13:08 +01:00
return -1;
2015-03-18 19:19:32 +01:00
/* LCOV_EXCL_STOP */
}
2014-11-12 20:41:00 +01:00
v[i][j - 1] = d;
2013-12-29 22:13:08 +01:00
}
}
}
return 0;
}