# cpuinfo: reading the host processor once so the hot path can branch on it

> cpuinfo is a BSD-2-Clause licensed C library that detects instruction sets, processor names, microarchitectures and cache topology on x86, ARM and ARM64 across six operating systems, with no memory allocation after initialization and no exceptions. It is the library PyTorch uses to know what the CPU can do.

**pytorch/cpuinfo** — CPU INFOrmation library (x86/x86-64/ARM/ARM64, Linux/Windows/Android/macOS/iOS)

- Repository: https://github.com/pytorch/cpuinfo
- Stars: 1,189 · Forks: 415
- Language: C
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/pytorch-cpuinfo

## One initialisation, then no allocation and no exceptions

cpuinfo is a library to detect essential for performance optimization information about host CPU, and the two constraints that shape the API are the interesting part of its design.

The interface is modern C and C++, it is thread-safe, there is no memory allocation after initialization, and no exceptions are thrown. Those last two are the ones that matter for the use case the library exists to serve.

If your program has a hot path that branches on whether the CPU supports AVX or NEON, you do not want to re-run the detection on every call, and you certainly do not want a heap allocation in a branch that is supposed to be cheap. So the detection happens once, through cpuinfo_initialize, and after that the library hands back pointers to data that was allocated during that one call.

The exception-free guarantee follows from the same reasoning. A library that is called during process startup, potentially before the runtime is in a good state, should not throw. Instead it returns status codes.

The result is an API where the cost of asking a question is a pointer dereference, which is what makes the examples in the README cheap enough to be worth reading. The simplest one logs the processor name:

```c
cpuinfo_initialize();
printf("Running on %s CPU\n", cpuinfo_get_package(0)->name);
```

And the feature check is a single boolean:

```c
cpuinfo_initialize();
if (cpuinfo_has_x86_avx()) {
    avx_implementation(arguments);
}
```

The library is BSD-2-Clause licensed, and the project is under the pytorch organisation, which tells you the original motivation was to let a tensor library dispatch to the best kernel for the processor it finds itself on.

## Instruction sets, microarchitecture, and per-core identity

The detection of supported instruction sets goes up to AVX512 on x86 and ARMv8.3 extensions, and the architecture is per core rather than per package.

That last point is the one that catches people out on modern hardware. The processor name is the SoC name, but the microarchitecture and the ID are reported for each CPU core separately, and the ID is expressed differently per architecture: it is the MIDR on ARM and the CPUID leaf 1 EAX value on x86.

So on a heterogeneous system you get different microarchitectures per core, and the README gives an example that switches on the microarchitecture of whichever core the current thread is running on:

```c
cpuinfo_initialize();
switch (cpuinfo_get_current_core()->uarch) {
    case cpuinfo_uarch_cortex_a53:
        cortex_a53_implementation(arguments);
        break;
    default:
        generic_implementation(arguments);
        break;
}
```

The library also supports systems with heterogeneous cores such as big.LITTLE and Max.Med.Min, and the cache example is written with that in mind, since it asks for the level 1 data cache size on the fastest core in the processor, the big core in a big.LITTLE ARM system:

```c
cpuinfo_initialize();
const size_t l1_size = cpuinfo_get_processor(0)->cache.l1d->size;
```

Note that this takes processor 0 while the microarchitecture example takes the current core. The distinction between a processor, a core and a package is a bit of vocabulary you have to pick up, and the naming follows it: cpuinfo_get_package, cpuinfo_get_processor, cpuinfo_get_current_core.

There is also a compile-time variant for when the answer is decided at build time rather than at runtime, using the architecture macros directly:

```c
#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
    /* 32-bit ARM-specific code here */
#endif
```

That is the right tool when you are building a binary for one target architecture and do not want to pay for runtime detection.

## Cache topology, and the affinity trick it enables

Cache detection goes beyond the size. The library reports the cache type, whether it is instruction, data or unified, the size and the line size, the associativity, and the number of cores and logical processors that share it.

That last item is the one with practical consequences, and the README shows what you can do with it. Because the library knows which logical processors share a given cache level, you can pin a thread to the set of cores that share the L2 cache of the core it is currently on:

```c
cpuinfo_initialize();
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
const struct cpuinfo_cache* current_l2 = cpuinfo_get_current_processor()->cache.l2;
for (uint32_t i = 0; i < current_l2->processor_count; i++) {
    CPU_SET(cpuinfo_get_processor(current_l2->processor_start + i)->linux_id, &cpu_set);
}
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_set);
```

The pattern is worth understanding. You ask for the cache of the current processor, read the processor_count and processor_start fields, and then build a CPU set from the processors in that range. This is a Linux and Android specific path, since it uses the affinity syscall, and the linux_id field is what maps a logical processor to the operating system's CPU numbering.

The general topology information is reported relative between logical processors, cores and processor packages, so you can also answer the simpler question of how many physical cores a machine has and which logical processors belong to which core, without parsing the OS's own topology files.

For the hierarchy itself, the exposed information list is explicit about what is done. Cache size, associativity, line size, number of partitions, the flags such as unified or inclusive or complex hash function, and the topology of logical processors sharing that cache level are all ticked. TLB information is not ticked at all, with number of entries, associativity, covered page types and covered page sizes all left open. CPU frequency is also unticked.

## Build system integration through pkg-config

The library generates a pkg-config manifest, libcpuinfo.pc, by default when built, and the README documents integration with five build systems, which is a good sign for how portable the project is meant to be.

The command line check is the quickest way to confirm the library is visible to your build environment:

```console
$ pkg-config --cflags --libs libcpuinfo
-I/usr/include/x86_64-linux-gnu/ -L/lib/x86_64-linux-gnu/ -lcpuinfo
```

And if you installed from source into a non-standard prefix, pkg-config needs help finding it:

```console
$ PKG_CONFIG_PATH="/home/me/projects/cpuinfo/prefix/lib/pkgconfig/:$PKG_CONFIG_PATH" pkg-config --cflags --libs libcpuinfo
-I/home/me/projects/cpuinfo/prefix/include -L/home/me/projects/cpuinfo/prefix/lib -lcpuinfo
```

For CMake, the FindPkgConfig module is the route, and the imported target is created for you:

```cmake
cmake_minimum_required(VERSION 3.6)
project("MyCpuInfoProject")

find_package(PkgConfig)
pkg_check_modules(CpuInfo REQUIRED IMPORTED_TARGET libcpuinfo)

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PkgConfig::CpuInfo)
```

For Meson it is a single dependency line, dependency('libcpuinfo'), and the example project block shows the minimum meson_version of 0.55.0.

For GNU Autotools the include is the PKG_CHECK_MODULES macro against libcpuinfo, with an AC_MSG_ERROR fallback that reports libcpuinfo missing, and the flags then copied into YOURPROJECT_CXXFLAGS and YOURPROJECT_LIBS.

For Bazel the library is in the Bazel Central Registry, and the MODULE.bazel example shows both the bazel_dep with a version and a git_override with a commit hash and the remote URL, which is the same pinning decision you would make with any other dependency.

And for a plain Makefile, the flags are pulled in with shell substitution into CFLAGS and LDFLAGS.

The repository layout supports all of this: CMakeLists.txt and CMakePresets.json at the root, BUILD.bazel and MODULE.bazel for Bazel, a libcpuinfo.pc.in template, a configure.py and a confu.yaml for the Python-driven configuration, cmake/ and scripts/ directories, a bench/ directory for benchmarks, and include/ and src/ for the library itself.

## Testing against real devices, and the parts of the tree that are not for you

The quality claim in the feature list is specific, and it is the kind of claim that is either verifiable or not.

The library is described as well-tested production-quality code, with 60+ mock tests based on data from real devices, work-arounds for common bugs in hardware and OS kernels, and support for systems with heterogeneous cores such as big.LITTLE and Max.Med.Min.

Mock tests built from real device data is a specific and sensible approach for a library like this. The alternative, running against whatever CPU the developer happens to have, would only ever exercise one vendor's behaviour, and the whole value of the library is that it gets the answer right on the machine it is running on. The work-arounds for hardware and kernel bugs are the part you cannot test yourself, since you would need the broken machine.

The repository layout also shows that this tree serves more than one consumer. There is a jni/ directory, which is the Java native interface layer, and a deps/ directory, plus a test/ directory and a tools/ directory. The JNI bindings exist because of the PyTorch connection, so a plain C or C++ user can ignore that part of the tree entirely.

The build badges show Travis CI for Linux and Mac and AppVeyor for Windows, so the platform matrix is covered by continuous integration rather than by a claim. The repository is not archived, the last push was on 2026-07-30, and there are no GitHub releases, so the version you get is whatever you build or whatever a package manager provides.

The licence is the Simplified BSD, and the project is deliberately permissive about it, calling it a permissive open-source license. For a library that you may end up vendoring into a larger codebase, that is the right choice.

## Conclusion

cpuinfo is a good pick for a C or C++ project that dispatches to different code paths depending on what the processor can do, because the detection is per core and per instruction set rather than a single guess, and the library is small enough to vendor; it is the wrong pick if you need frequency information, since CPU frequency is an unticked box in the exposed information list, and before you adopt it you should read the top level directory, because the jni/ and deps/ directories indicate that parts of the tree are for other consumers rather than for a plain C library user.

## FAQ

### What does the cpuinfo library detect?

The processor or SoC name, the vendor and microarchitecture for each CPU core, the core ID as the MIDR on ARM or the CPUID leaf 1 EAX value on x86, the supported instruction sets up to AVX512 and ARMv8.3 extensions, cache type, size, line size, associativity and sharing topology, and the general topology between logical processors, cores and packages.

### How do I use cpuinfo in a C program?

Call cpuinfo_initialize once, then query the data. For example printf the processor name with cpuinfo_get_package(0)->name, check for a feature with cpuinfo_has_x86_avx() or cpuinfo_has_arm_neon(), and switch on the microarchitecture of the current core with cpuinfo_get_current_core()->uarch.

### Is cpuinfo safe to use in a hot path?

Yes, and the design is built around that. The library is thread-safe, performs no memory allocation after initialization and throws no exceptions, so once cpuinfo_initialize has run the queries are pointer dereferences into data allocated during that single call.

### How do I link cpuinfo in my build?

The library generates a libcpuinfo.pc pkg-config manifest, so you can use pkg-config --cflags --libs libcpuinfo, the FindPkgConfig module in CMake with pkg_check_modules, dependency('libcpuinfo') in Meson, PKG_CHECK_MODULES in Autotools, a bazel_dep from the Bazel Central Registry, or direct shell substitution in a Makefile.

### What licence is cpuinfo released under?

The Simplified BSD License, with the text in the LICENSE file at the repository root. The repository is not archived, the last push was on 2026-07-30, and there are no GitHub releases.

## Sources

- [Issues](https://github.com/pytorch/cpuinfo/issues)
- [License: BSD-2-Clause](https://github.com/pytorch/cpuinfo/blob/main/LICENSE)
- [pytorch/cpuinfo on GitHub](https://github.com/pytorch/cpuinfo)
- [README](https://github.com/pytorch/cpuinfo/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pytorch-cpuinfo
