cache line locking on AMD x86_64 utilising L3 CAT pseudo-locking
26

Configure Feed

Select the types of activity you want to include in your feed.

icepick / src / lock.c
13 kB 405 lines
1#define _GNU_SOURCE 2#include "internal.h" 3#include <stdlib.h> 4#include <sched.h> 5#include <unistd.h> 6#include <string.h> 7#include <x86intrin.h> 8 9static uint32_t compute_way_mask(unsigned num_ways, unsigned start_way) 10{ 11 uint32_t mask = 0; 12 for (unsigned i = 0; i < num_ways; i++) 13 mask |= (1U << (start_way + i)); 14 return mask; 15} 16 17static unsigned find_start_way(uint32_t default_mask, unsigned num_ways) 18{ 19 for (unsigned i = 0; i <= 32 - num_ways; i++) { 20 uint32_t candidate = compute_way_mask(num_ways, i); 21 if ((candidate & default_mask) == candidate) 22 return i; 23 } 24 return 0; 25} 26 27static void init_pointer_chase(volatile char *ptr, size_t size) 28{ 29 size_t num_lines = size / CACHE_LINE_SIZE; 30 volatile size_t *indices = (volatile size_t *)ptr; 31 32 for (size_t i = 0; i < num_lines; i++) 33 indices[i * (CACHE_LINE_SIZE / sizeof(size_t))] = ((i + 1) % num_lines) * CACHE_LINE_SIZE; 34 35 for (size_t i = num_lines - 1; i > 0; i--) { 36 size_t j = (size_t)rdtsc_start() % (i + 1); 37 size_t idx_i = i * (CACHE_LINE_SIZE / sizeof(size_t)); 38 size_t idx_j = j * (CACHE_LINE_SIZE / sizeof(size_t)); 39 size_t tmp = indices[idx_i]; 40 indices[idx_i] = indices[idx_j]; 41 indices[idx_j] = tmp; 42 } 43} 44 45static void prime_temporal_sequential(volatile char *ptr, size_t size) 46{ 47 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) 48 (void)ptr[offset]; 49} 50 51static void prime_temporal_reverse(volatile char *ptr, size_t size) 52{ 53 for (size_t offset = size; offset > 0; offset -= CACHE_LINE_SIZE) 54 (void)ptr[offset - CACHE_LINE_SIZE]; 55} 56 57static void prime_temporal_strided(volatile char *ptr, size_t size, size_t stride) 58{ 59 size_t effective_stride = stride ? stride : (CACHE_LINE_SIZE * 8); 60 for (size_t pass = 0; pass < effective_stride; pass += CACHE_LINE_SIZE) { 61 for (size_t offset = pass; offset < size; offset += effective_stride) 62 (void)ptr[offset]; 63 } 64} 65 66static void prime_temporal_chase(volatile char *ptr, size_t size) 67{ 68 volatile size_t *indices = (volatile size_t *)ptr; 69 size_t num_lines = size / CACHE_LINE_SIZE; 70 size_t offset = 0; 71 72 for (size_t i = 0; i < num_lines; i++) { 73 offset = indices[offset / sizeof(size_t)]; 74 } 75 76 (void)offset; 77} 78 79static void prime_prefetcht2_sequential(volatile char *ptr, size_t size) 80{ 81 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) 82 _mm_prefetch((const char *)&ptr[offset], _MM_HINT_T2); 83 _mm_mfence(); 84 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) 85 (void)ptr[offset]; 86} 87 88static void prime_prefetcht2_reverse(volatile char *ptr, size_t size) 89{ 90 for (size_t offset = size; offset > 0; offset -= CACHE_LINE_SIZE) 91 _mm_prefetch((const char *)&ptr[offset - CACHE_LINE_SIZE], _MM_HINT_T2); 92 _mm_mfence(); 93 for (size_t offset = size; offset > 0; offset -= CACHE_LINE_SIZE) 94 (void)ptr[offset - CACHE_LINE_SIZE]; 95} 96 97static void prime_prefetcht2_strided(volatile char *ptr, size_t size, size_t stride) 98{ 99 size_t effective_stride = stride ? stride : (CACHE_LINE_SIZE * 8); 100 for (size_t pass = 0; pass < effective_stride; pass += CACHE_LINE_SIZE) { 101 for (size_t offset = pass; offset < size; offset += effective_stride) 102 _mm_prefetch((const char *)&ptr[offset], _MM_HINT_T2); 103 } 104 _mm_mfence(); 105 for (size_t pass = 0; pass < effective_stride; pass += CACHE_LINE_SIZE) { 106 for (size_t offset = pass; offset < size; offset += effective_stride) 107 (void)ptr[offset]; 108 } 109} 110 111static void prime_prefetchnta_sequential(volatile char *ptr, size_t size) 112{ 113 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) 114 _mm_prefetch((const char *)&ptr[offset], _MM_HINT_NTA); 115 _mm_mfence(); 116 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) 117 (void)ptr[offset]; 118} 119 120static void prime_nt_store_sequential(volatile char *ptr, size_t size) 121{ 122 __m128i zero = _mm_setzero_si128(); 123 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) { 124 _mm_stream_si128((__m128i *)&ptr[offset], zero); 125 _mm_stream_si128((__m128i *)&ptr[offset + 16], zero); 126 _mm_stream_si128((__m128i *)&ptr[offset + 32], zero); 127 _mm_stream_si128((__m128i *)&ptr[offset + 48], zero); 128 } 129 _mm_sfence(); 130 for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) 131 (void)ptr[offset]; 132} 133 134void prime_region(volatile char *ptr, size_t size, unsigned iterations, 135 icepick_prime_strategy_t strategy, icepick_access_pattern_t pattern, 136 size_t stride) 137{ 138 if (pattern == ICEPICK_PATTERN_POINTER_CHASE) 139 init_pointer_chase(ptr, size); 140 141 for (unsigned iter = 0; iter < iterations; iter++) { 142 switch (strategy) { 143 case ICEPICK_PRIME_TEMPORAL: 144 switch (pattern) { 145 case ICEPICK_PATTERN_SEQUENTIAL: 146 prime_temporal_sequential(ptr, size); 147 break; 148 case ICEPICK_PATTERN_REVERSE: 149 prime_temporal_reverse(ptr, size); 150 break; 151 case ICEPICK_PATTERN_STRIDED: 152 prime_temporal_strided(ptr, size, stride); 153 break; 154 case ICEPICK_PATTERN_POINTER_CHASE: 155 prime_temporal_chase(ptr, size); 156 break; 157 } 158 break; 159 160 case ICEPICK_PRIME_PREFETCHT2: 161 switch (pattern) { 162 case ICEPICK_PATTERN_SEQUENTIAL: 163 prime_prefetcht2_sequential(ptr, size); 164 break; 165 case ICEPICK_PATTERN_REVERSE: 166 prime_prefetcht2_reverse(ptr, size); 167 break; 168 case ICEPICK_PATTERN_STRIDED: 169 prime_prefetcht2_strided(ptr, size, stride); 170 break; 171 case ICEPICK_PATTERN_POINTER_CHASE: 172 prime_prefetcht2_sequential(ptr, size); 173 prime_temporal_chase(ptr, size); 174 break; 175 } 176 break; 177 178 case ICEPICK_PRIME_PREFETCHNTA: 179 prime_prefetchnta_sequential(ptr, size); 180 break; 181 182 case ICEPICK_PRIME_NT_STORE: 183 prime_nt_store_sequential(ptr, size); 184 break; 185 } 186 } 187} 188 189int icepick_lock(icepick_topology_t *topo, const icepick_config_t *cfg, 190 icepick_region_t **region) 191{ 192 if (!topo || !cfg || !region) 193 return ICEPICK_E_INVALID; 194 195 if (cfg->clos_id == 0 || cfg->clos_id >= topo->max_clos) 196 return ICEPICK_E_INVALID; 197 198 unsigned ways_needed = (cfg->size + topo->way_size - 1) / topo->way_size; 199 if (ways_needed == 0) 200 ways_needed = 1; 201 202 if (ways_needed >= topo->l3_ways) 203 return ICEPICK_E_TOO_LARGE; 204 205 int ret = clos_init(topo); 206 if (ret < 0) 207 return ret; 208 209 unsigned start_way = find_start_way(topo->default_way_mask, ways_needed); 210 uint32_t way_mask = compute_way_mask(ways_needed, start_way); 211 212 ret = clos_allocate(cfg->clos_id, way_mask); 213 if (ret < 0) 214 return ret; 215 216 icepick_region_t *r = calloc(1, sizeof(*r)); 217 if (!r) { 218 clos_release(cfg->clos_id); 219 return ICEPICK_E_ALLOC; 220 } 221 222 r->clos_id = cfg->clos_id; 223 r->numa_node = cfg->numa_node; 224 r->way_mask = way_mask; 225 r->mba_throttle = cfg->mba_throttle; 226 r->core_type = cfg->core_type; 227 r->topo = topo; 228 r->size = ways_needed * topo->way_size; 229 r->monitor = NULL; 230 r->prime_strategy = cfg->prime_strategy; 231 r->access_pattern = cfg->access_pattern; 232 r->stride_bytes = cfg->stride_bytes; 233 r->prime_iterations = cfg->prime_iterations ? cfg->prime_iterations : 3; 234 235 ret = region_alloc(r->size, cfg->numa_node, cfg->huge_pages, &r->ptr); 236 if (ret < 0) { 237 clos_release(cfg->clos_id); 238 free(r); 239 return ret; 240 } 241 242 int cpu = find_cpu_for_core_type(topo, cfg->core_type, cfg->numa_node); 243 244 cpu_set_t old_affinity, new_affinity; 245 CPU_ZERO(&new_affinity); 246 CPU_SET(cpu, &new_affinity); 247 sched_getaffinity(0, sizeof(old_affinity), &old_affinity); 248 sched_setaffinity(0, sizeof(new_affinity), &new_affinity); 249 250 if (topo->arch == ARCH_X86_64) { 251 int msr_fd = msr_open(cpu); 252 if (msr_fd < 0) { 253 sched_setaffinity(0, sizeof(old_affinity), &old_affinity); 254 region_free(r->ptr, r->size); 255 clos_release(cfg->clos_id); 256 free(r); 257 return msr_fd; 258 } 259 260 ret = clos_configure_mask(msr_fd, 0, clos_table[0].way_mask); 261 if (ret < 0) { 262 msr_close(msr_fd); 263 goto cleanup_no_msr; 264 } 265 266 ret = clos_configure_mask(msr_fd, cfg->clos_id, way_mask); 267 if (ret < 0) { 268 msr_close(msr_fd); 269 goto cleanup_no_msr; 270 } 271 272 if (topo->mba_supported && cfg->mba_throttle > 0) { 273 ret = mba_configure(msr_fd, cfg->clos_id, cfg->mba_throttle); 274 if (ret < 0) { 275 msr_close(msr_fd); 276 goto cleanup_no_msr; 277 } 278 } 279 280 ret = clos_associate_thread(msr_fd, cfg->clos_id); 281 if (ret < 0) { 282 msr_close(msr_fd); 283 goto cleanup_no_msr; 284 } 285 286 prime_region(r->ptr, r->size, r->prime_iterations, 287 r->prime_strategy, r->access_pattern, r->stride_bytes); 288 289 clos_associate_thread(msr_fd, 0); 290 msr_close(msr_fd); 291 } else if (topo->arch == ARCH_ARM64) { 292 ret = mpam_configure_partition(cfg->clos_id, way_mask, cfg->mba_throttle); 293 if (ret < 0) 294 goto cleanup_no_msr; 295 296 ret = mpam_associate_thread(cfg->clos_id); 297 if (ret < 0) 298 goto cleanup_no_msr; 299 300 prime_region(r->ptr, r->size, r->prime_iterations, 301 r->prime_strategy, r->access_pattern, r->stride_bytes); 302 303 mpam_associate_thread(0); 304 } 305 306 sched_setaffinity(0, sizeof(old_affinity), &old_affinity); 307 308 if (cfg->verify) { 309 icepick_latency_stats_t stats; 310 ret = icepick_verify(r, &stats); 311 if (ret < 0) { 312 icepick_unlock(r); 313 return ret; 314 } 315 } 316 317 if (cfg->auto_monitor) { 318 icepick_monitor_t *mon; 319 ret = icepick_monitor_start_ex(r, cfg->pmu_poll_interval_ns, 320 cfg->probe_interval_ns, 321 cfg->miss_threshold, &mon); 322 if (ret < 0) { 323 icepick_unlock(r); 324 return ret; 325 } 326 r->monitor = mon; 327 } 328 329 *region = r; 330 return 0; 331 332cleanup_no_msr: 333 sched_setaffinity(0, sizeof(old_affinity), &old_affinity); 334 region_free(r->ptr, r->size); 335 clos_release(cfg->clos_id); 336 free(r); 337 return ret; 338} 339 340int icepick_unlock(icepick_region_t *region) 341{ 342 if (!region) 343 return ICEPICK_E_INVALID; 344 345 if (region->monitor) 346 icepick_monitor_stop(region->monitor); 347 348 if (region->topo->arch == ARCH_X86_64) { 349 int cpu = sched_getcpu(); 350 if (cpu < 0) 351 cpu = 0; 352 353 int msr_fd = msr_open(cpu); 354 if (msr_fd >= 0) { 355 clos_release(region->clos_id); 356 clos_configure_mask(msr_fd, region->clos_id, 0); 357 if (region->topo->mba_supported && region->mba_throttle > 0) 358 mba_configure(msr_fd, region->clos_id, 0); 359 clos_configure_mask(msr_fd, 0, clos_get_default_mask()); 360 msr_close(msr_fd); 361 } 362 } else if (region->topo->arch == ARCH_ARM64) { 363 clos_release(region->clos_id); 364 mpam_configure_partition(region->clos_id, 0, 0); 365 } 366 367 region_free(region->ptr, region->size); 368 free(region); 369 370 return 0; 371} 372 373void *icepick_region_ptr(const icepick_region_t *region) 374{ 375 return region ? region->ptr : NULL; 376} 377 378size_t icepick_region_size(const icepick_region_t *region) 379{ 380 return region ? region->size : 0; 381} 382 383unsigned icepick_region_clos(const icepick_region_t *region) 384{ 385 return region ? region->clos_id : 0; 386} 387 388const char *icepick_strerror(int err) 389{ 390 switch (err) { 391 case 0: return "success"; 392 case ICEPICK_E_NO_CAT: return "cat not supported on this cpu"; 393 case ICEPICK_E_PERMISSION: return "permission denied (need root or cap_sys_rawio)"; 394 case ICEPICK_E_NO_CLOS: return "no clos available or already allocated"; 395 case ICEPICK_E_TOO_LARGE: return "requested size exceeds available cache ways"; 396 case ICEPICK_E_NUMA: return "invalid numa node or binding failed"; 397 case ICEPICK_E_HUGEPAGE: return "huge page allocation failed"; 398 case ICEPICK_E_VERIFY: return "verification failed - data may not be cache-resident"; 399 case ICEPICK_E_INVALID: return "invalid argument"; 400 case ICEPICK_E_ALLOC: return "memory allocation failed"; 401 case ICEPICK_E_MSR: return "msr operation failed"; 402 case ICEPICK_E_THREAD: return "monitor thread creation failed"; 403 default: return "unknown error"; 404 } 405}