test_row_store.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. """CPU parity, eviction and simultaneous callers on an unaligned fixture."""
  2. import concurrent.futures
  3. import ctypes as C
  4. import json
  5. import os
  6. from pathlib import Path
  7. import random
  8. import tempfile
  9. lib = C.CDLL(str(Path(__file__).resolve().parents[1]/'adapter/librow_store.so'))
  10. U = C.c_uint64
  11. P = C.c_void_p
  12. lib.row_store_open.argtypes = [C.c_char_p, U, U, U, U]
  13. lib.row_store_open.restype = P
  14. lib.row_store_close.argtypes = [P]
  15. lib.row_store_stats.argtypes = [P, C.POINTER(U)]
  16. class Work(C.Structure):
  17. _fields_ = [('store', P), ('ids', P), ('weights', P), ('scales', P), ('count', U)]
  18. lib.row_store_lookup.argtypes = [C.POINTER(Work)]
  19. lib.row_store_range.argtypes = [P, U, U]
  20. rows = 4099
  21. rng = random.Random(413)
  22. weights, scales = rng.randbytes(rows * 256), rng.randbytes(rows * 8)
  23. offset = 777
  24. with tempfile.NamedTemporaryFile() as f:
  25. f.write(bytes(offset) + weights + scales)
  26. f.flush()
  27. # (budget, owned range): a narrowed range zeroes unowned rows and pins the
  28. # owned scale shard, so the pinned and unpinned miss paths both get covered.
  29. cases = [(0, None), (272 * 17, None), (272 * rows, None),
  30. (272 * rows, (0, rows)), (1088 * 64, (1000, 3000))]
  31. for budget, owned in cases:
  32. store = lib.row_store_open(f.name.encode(), rows, offset, offset + len(weights), budget)
  33. assert store
  34. lo, hi = owned if owned else (0, rows)
  35. if owned:
  36. lib.row_store_range(store, lo, hi)
  37. def expected(i, table, width):
  38. if not (lo <= i < hi):
  39. return bytes(width)
  40. return table[i*width:(i+1)*width]
  41. def check(seed):
  42. r = random.Random(seed)
  43. ids = [0, rows - 1, 15, 16, 17, 0, 17] + [r.randrange(rows) for _ in range(1000)]
  44. indices = (C.c_int64 * len(ids))(*ids)
  45. w, s = C.create_string_buffer(len(ids) * 256), C.create_string_buffer(len(ids) * 8)
  46. work = Work(store, C.addressof(indices), C.addressof(w), C.addressof(s), len(ids))
  47. lib.row_store_lookup(C.byref(work))
  48. assert w.raw == b''.join(expected(i, weights, 256) for i in ids)
  49. assert s.raw == b''.join(expected(i, scales, 8) for i in ids)
  50. return sum(1 for i in ids if lo <= i < hi)
  51. with concurrent.futures.ThreadPoolExecutor(4) as pool:
  52. checked = sum(pool.map(check, range(12)))
  53. stats = (U * 9)()
  54. lib.row_store_stats(store, stats)
  55. assert stats[0] + stats[1] == checked
  56. assert stats[3] <= budget
  57. pinned = os.environ.get('DSV41_RESIDENT_SCALES', '1') not in ('0', 'off', 'false')
  58. if owned and pinned:
  59. assert stats[5] == (hi - lo) * 8
  60. print(json.dumps(dict(budget=budget, owned=owned, checked=checked, hits=stats[0],
  61. misses=stats[1], reads=stats[2], slots=stats[4],
  62. scale_bytes=stats[5], ways=stats[6], threads=stats[7],
  63. passed=True)))
  64. lib.row_store_close(store)