Skip to content

Commit 16528ff

Browse files
committed
Implement a cache for cpu_info
Fixes #349 This provides a fix for #349 --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/Blosc/python-blosc2/issues/349?shareId=XXXX-XXXX-XXXX-XXXX).
1 parent 256313f commit 16528ff

File tree

2 files changed

+79
-16
lines changed

2 files changed

+79
-16
lines changed

src/blosc2/core.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
import copy
1212
import ctypes
1313
import ctypes.util
14+
import json
1415
import math
1516
import os
1617
import pathlib
1718
import pickle
1819
import platform
1920
import sys
2021
from dataclasses import asdict
22+
from functools import lru_cache
2123
from typing import TYPE_CHECKING
2224

2325
import cpuinfo
@@ -1149,22 +1151,37 @@ def linux_cache_size(cache_level: int, default_size: int) -> int:
11491151
return cache_size
11501152

11511153

1152-
def get_cpu_info():
1153-
cpu_info = cpuinfo.get_cpu_info()
1154-
# cpuinfo does not correctly retrieve the cache sizes for Apple Silicon, so do it manually
1155-
if platform.system() == "Darwin":
1156-
cpu_info["l1_data_cache_size"] = apple_silicon_cache_size(1)
1157-
cpu_info["l2_cache_size"] = apple_silicon_cache_size(2)
1158-
cpu_info["l3_cache_size"] = apple_silicon_cache_size(3)
1159-
# cpuinfo does not correctly retrieve the cache sizes for all CPUs on Linux, so ask the kernel
1160-
if platform.system() == "Linux":
1161-
l1_data_cache_size = cpu_info.get("l1_data_cache_size", 32 * 1024)
1162-
cpu_info["l1_data_cache_size"] = linux_cache_size(1, l1_data_cache_size)
1163-
l2_cache_size = cpu_info.get("l2_cache_size", 256 * 1024)
1164-
cpu_info["l2_cache_size"] = linux_cache_size(2, l2_cache_size)
1165-
l3_cache_size = cpu_info.get("l3_cache_size", 1024 * 1024)
1166-
cpu_info["l3_cache_size"] = linux_cache_size(3, l3_cache_size)
1167-
return cpu_info
1154+
def write_cached_cpu_info(cpu_info_dict: dict[str, any]) -> None:
1155+
with open(Path.home() / '.blosc2-cpuinfo.json', 'w') as f:
1156+
json.dump(cpu_info_dict, f, indent=4)
1157+
1158+
1159+
def read_cached_cpu_info() -> dict:
1160+
try:
1161+
with open(Path.home() / '.blosc2-cpuinfo.json', 'r') as f:
1162+
return json.load(f)
1163+
except (FileNotFoundError, json.JSONDecodeError):
1164+
return {}
1165+
1166+
1167+
@lru_cache(maxsize=1)
1168+
def get_cpu_info() -> dict:
1169+
cached_info = read_cached_cpu_info()
1170+
if cached_info:
1171+
return cached_info
1172+
1173+
try:
1174+
import cpuinfo
1175+
except ImportError:
1176+
return {}
1177+
cpu_info_dict = cpuinfo.get_cpu_info()
1178+
try:
1179+
write_cached_cpu_info(cpu_info_dict)
1180+
except IOError:
1181+
# cpu info cannot be stored.
1182+
# will need to be recomputed in the next process
1183+
pass
1184+
return cpu_info_dict
11681185

11691186

11701187
def get_blocksize() -> int:

tests/test_cpu_info_cache.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import json
2+
import os
3+
import unittest
4+
from unittest.mock import patch, mock_open
5+
from pathlib import Path
6+
7+
from blosc2.core import write_cached_cpu_info, read_cached_cpu_info, get_cpu_info
8+
9+
10+
class TestCpuInfoCache(unittest.TestCase):
11+
12+
@patch("builtins.open", new_callable=mock_open)
13+
def test_write_cached_cpu_info(self, mock_file):
14+
cpu_info_dict = {"brand": "Intel", "arch": "x86_64"}
15+
write_cached_cpu_info(cpu_info_dict)
16+
mock_file.assert_called_once_with(Path.home() / '.blosc2-cpuinfo.json', 'w')
17+
mock_file().write.assert_called_once_with(json.dumps(cpu_info_dict, indent=4))
18+
19+
@patch("builtins.open", new_callable=mock_open, read_data='{"brand": "Intel", "arch": "x86_64"}')
20+
def test_read_cached_cpu_info(self, mock_file):
21+
expected_cpu_info = {"brand": "Intel", "arch": "x86_64"}
22+
cpu_info = read_cached_cpu_info()
23+
mock_file.assert_called_once_with(Path.home() / '.blosc2-cpuinfo.json', 'r')
24+
self.assertEqual(cpu_info, expected_cpu_info)
25+
26+
@patch("blosc2.core.read_cached_cpu_info", return_value={"brand": "Intel", "arch": "x86_64"})
27+
def test_get_cpu_info_cached(self, mock_read_cached_cpu_info):
28+
expected_cpu_info = {"brand": "Intel", "arch": "x86_64"}
29+
cpu_info = get_cpu_info()
30+
mock_read_cached_cpu_info.assert_called_once()
31+
self.assertEqual(cpu_info, expected_cpu_info)
32+
33+
@patch("blosc2.core.read_cached_cpu_info", return_value={})
34+
@patch("blosc2.core.write_cached_cpu_info")
35+
@patch("blosc2.core.cpuinfo.get_cpu_info", return_value={"brand": "Intel", "arch": "x86_64"})
36+
def test_get_cpu_info_not_cached(self, mock_get_cpu_info, mock_write_cached_cpu_info, mock_read_cached_cpu_info):
37+
expected_cpu_info = {"brand": "Intel", "arch": "x86_64"}
38+
cpu_info = get_cpu_info()
39+
mock_read_cached_cpu_info.assert_called_once()
40+
mock_get_cpu_info.assert_called_once()
41+
mock_write_cached_cpu_info.assert_called_once_with(expected_cpu_info)
42+
self.assertEqual(cpu_info, expected_cpu_info)
43+
44+
45+
if __name__ == "__main__":
46+
unittest.main()

0 commit comments

Comments
 (0)