From fa3b28a6ca682d01b3f31427833f0580a79f26be Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Sun, 13 Jul 2025 21:57:06 +0800 Subject: [PATCH 01/26] Support for >64 CPU systems --- src/cpu.cpp | 741 ++++++++++++++++++++++++++++++++------- src/cpu.h | 66 +++- tests/CMakeLists.txt | 7 + tests/test_cpu_large.cpp | 250 +++++++++++++ 4 files changed, 930 insertions(+), 134 deletions(-) create mode 100644 tests/test_cpu_large.cpp diff --git a/src/cpu.cpp b/src/cpu.cpp index 4ba23ebea..ba806e3b7 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -1,16 +1,5 @@ -// Tencent is pleased to support the open source community by making ncnn available. -// -// Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. -// -// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except -// in compliance with the License. You may obtain a copy of the License at -// -// https://opensource.org/licenses/BSD-3-Clause -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. +// Copyright 2017 Tencent +// SPDX-License-Identifier: BSD-3-Clause #include "cpu.h" @@ -24,6 +13,8 @@ #include #include #include +#include +#include #ifdef _OPENMP #if NCNN_SIMPLEOMP @@ -185,6 +176,7 @@ __attribute__((constructor)) void ncnn_kmp_env_initializer() static int g_cpucount; static int g_physical_cpucount; static int g_powersave; +static int g_max_cpu_count = 0; // Maximum CPU count detected at runtime static ncnn::CpuSet g_cpu_affinity_mask_all; static ncnn::CpuSet g_cpu_affinity_mask_little; static ncnn::CpuSet g_cpu_affinity_mask_big; @@ -919,24 +911,58 @@ static int get_cpucount() } #if defined __ANDROID__ || defined __linux__ -static int get_thread_siblings(int cpuid) +static void get_thread_siblings(int cpuid, ncnn::CpuSet& siblings) { + siblings.disable_all(); + char path[256]; sprintf(path, "/sys/devices/system/cpu/cpu%d/topology/thread_siblings", cpuid); FILE* fp = 0; //fopen(path, "rb"); if (fp) { - int thread_siblings = -1; - int nscan = fscanf(fp, "%x", &thread_siblings); - if (nscan != 1) + // Try to read hex mask directly (this path is currently disabled) + char hex_str[256]; + int nscan = fscanf(fp, "%255s", hex_str); + if (nscan == 1) { - // ignore + // Parse hex string into CpuSet + int len = strlen(hex_str); + if (hex_str[0] == '0' && hex_str[1] == 'x') + { + // Skip "0x" prefix + len -= 2; + memmove(hex_str, hex_str + 2, len + 1); + } + + int ci = 0; + for (int i = len - 1; i >= 0; i--) + { + char c = hex_str[i]; + int hex_val = 0; + + if (c >= '0' && c <= '9') + hex_val = c - '0'; + else if (c >= 'a' && c <= 'f') + hex_val = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + hex_val = c - 'A' + 10; + else + continue; + + if (hex_val & 1) siblings.enable(ci + 0); + if (hex_val & 2) siblings.enable(ci + 1); + if (hex_val & 4) siblings.enable(ci + 2); + if (hex_val & 8) siblings.enable(ci + 3); + + ci += 4; + } } fclose(fp); - return thread_siblings; + if (!siblings.is_empty()) + return; } // second try, parse from human-readable thread_siblings_list @@ -945,8 +971,6 @@ static int get_thread_siblings(int cpuid) fp = fopen(path, "rb"); if (fp) { - int thread_siblings = -1; - int id0; char sep; int id1; @@ -954,36 +978,28 @@ static int get_thread_siblings(int cpuid) int nscan = fscanf(fp, "%d", &id0); if (nscan == 1) { - thread_siblings = (1 << id0); + siblings.enable(id0); while (fscanf(fp, "%c%d", &sep, &id1) == 2) { if (sep == ',') { - thread_siblings |= (1 << id1); + siblings.enable(id1); } if (sep == '-' && id0 < id1) { for (int i = id0 + 1; i <= id1; i++) { - thread_siblings |= (1 << i); + siblings.enable(i); } } id0 = id1; } } - else - { - // ignore - } fclose(fp); - - return thread_siblings; } - - return -1; } #endif // defined __ANDROID__ || defined __linux__ @@ -1020,11 +1036,12 @@ static int get_physical_cpucount() free(buffer); #elif defined __ANDROID__ || defined __linux__ - std::vector thread_set; + std::vector thread_set; for (int i = 0; i < g_cpucount; i++) { - int thread_siblings = get_thread_siblings(i); - if (thread_siblings == -1) + ncnn::CpuSet thread_siblings; + get_thread_siblings(i, thread_siblings); + if (thread_siblings.is_empty()) { // ignore malformed one continue; @@ -1033,7 +1050,18 @@ static int get_physical_cpucount() bool thread_siblings_exists = false; for (size_t j = 0; j < thread_set.size(); j++) { - if (thread_set[j] == thread_siblings) + // Compare CpuSets by checking if they have the same enabled CPUs + bool same = true; + int max_cpu = std::max(thread_siblings.max_cpu_id(), thread_set[j].max_cpu_id()); + for (int k = 0; k <= max_cpu; k++) + { + if (thread_siblings.is_enabled(k) != thread_set[j].is_enabled(k)) + { + same = false; + break; + } + } + if (same) { thread_siblings_exists = true; break; @@ -1156,11 +1184,24 @@ static int get_data_cache_size(int cpuid, int level) int ci = 0; for (int i = len - 1; i >= 0; i--) { - char x = shared_cpu_map_str[i]; - if (x & 1) shared_cpu_map.enable(ci + 0); - if (x & 2) shared_cpu_map.enable(ci + 1); - if (x & 4) shared_cpu_map.enable(ci + 2); - if (x & 8) shared_cpu_map.enable(ci + 3); + char c = shared_cpu_map_str[i]; + int hex_val = 0; + + // Convert hex character to value + if (c >= '0' && c <= '9') + hex_val = c - '0'; + else if (c >= 'a' && c <= 'f') + hex_val = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + hex_val = c - 'A' + 10; + else + continue; // Skip invalid characters + + // Set bits according to hex value + if (hex_val & 1) shared_cpu_map.enable(ci + 0); + if (hex_val & 2) shared_cpu_map.enable(ci + 1); + if (hex_val & 4) shared_cpu_map.enable(ci + 2); + if (hex_val & 8) shared_cpu_map.enable(ci + 3); ci += 4; } @@ -1172,14 +1213,15 @@ static int get_data_cache_size(int cpuid, int level) // resolve physical cpu count in the shared_cpu_map int shared_physical_cpu_count = 0; { - std::vector thread_set; + std::vector thread_set; for (int i = 0; i < g_cpucount; i++) { if (!shared_cpu_map.is_enabled(i)) continue; - int thread_siblings = get_thread_siblings(i); - if (thread_siblings == -1) + ncnn::CpuSet thread_siblings; + get_thread_siblings(i, thread_siblings); + if (thread_siblings.is_empty()) { // ignore malformed one continue; @@ -1188,7 +1230,18 @@ static int get_data_cache_size(int cpuid, int level) bool thread_siblings_exists = false; for (size_t j = 0; j < thread_set.size(); j++) { - if (thread_set[j] == thread_siblings) + // Compare CpuSets by checking if they have the same enabled CPUs + bool same = true; + int max_cpu = std::max(thread_siblings.max_cpu_id(), thread_set[j].max_cpu_id()); + for (int k = 0; k <= max_cpu; k++) + { + if (thread_siblings.is_enabled(k) != thread_set[j].is_enabled(k)) + { + same = false; + break; + } + } + if (same) { thread_siblings_exists = true; break; @@ -1376,11 +1429,17 @@ static ncnn::CpuSet get_smt_cpu_mask() if (ptr->Relationship == RelationProcessorCore) { ncnn::CpuSet smt_set; - smt_set.mask = ptr->ProcessorMask; + smt_set.set_legacy_mask(ptr->ProcessorMask); if (smt_set.num_enabled() > 1) { - // this core is smt - smt_cpu_mask.mask |= smt_set.mask; + // this core is smt - merge with existing smt_cpu_mask + for (int i = 0; i < 64; i++) // ProcessorMask is limited to 64 bits + { + if (smt_set.is_enabled(i)) + { + smt_cpu_mask.enable(i); + } + } } } @@ -1435,14 +1494,73 @@ static std::vector get_max_freq_mhz() static int set_sched_affinity(const ncnn::CpuSet& thread_affinity_mask) { - DWORD_PTR prev_mask = SetThreadAffinityMask(GetCurrentThread(), thread_affinity_mask.mask); - if (prev_mask == 0) + // Check if we can use the legacy method (<=64 CPUs) + int max_cpu = thread_affinity_mask.max_cpu_id(); + if (max_cpu < 64) { - NCNN_LOGE("SetThreadAffinityMask failed %d", GetLastError()); + ULONG_PTR legacy_mask = thread_affinity_mask.get_legacy_mask(); + if (legacy_mask != 0) + { + DWORD_PTR prev_mask = SetThreadAffinityMask(GetCurrentThread(), legacy_mask); + if (prev_mask == 0) + { + NCNN_LOGE("SetThreadAffinityMask failed %d", GetLastError()); + return -1; + } + return 0; + } + } + + // For >64 CPU support, use SetThreadGroupAffinity + // Windows organizes CPUs into groups of 64 + typedef BOOL(WINAPI * LPFN_STGA)(HANDLE, const GROUP_AFFINITY*, GROUP_AFFINITY*); + + HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + { + NCNN_LOGE("Failed to get kernel32.dll handle"); return -1; } - return 0; + LPFN_STGA SetThreadGroupAffinityFunc = (LPFN_STGA)GetProcAddress(kernel32, "SetThreadGroupAffinity"); + if (!SetThreadGroupAffinityFunc) + { + NCNN_LOGE("SetThreadGroupAffinity not available, >64 CPU affinity not supported"); + return -1; + } + + // Find the first enabled CPU and set affinity to its group + // This is a simplified implementation - ideally we'd handle multiple groups + for (int cpu = 0; cpu <= max_cpu; cpu++) + { + if (thread_affinity_mask.is_enabled(cpu)) + { + GROUP_AFFINITY group_affinity = {0}; + group_affinity.Group = (WORD)(cpu / 64); + group_affinity.Mask = 1ULL << (cpu % 64); + + // Add other CPUs in the same group + for (int other_cpu = cpu + 1; other_cpu <= max_cpu && other_cpu < (group_affinity.Group + 1) * 64; other_cpu++) + { + if (thread_affinity_mask.is_enabled(other_cpu)) + { + group_affinity.Mask |= 1ULL << (other_cpu % 64); + } + } + + GROUP_AFFINITY prev_affinity; + if (!SetThreadGroupAffinityFunc(GetCurrentThread(), &group_affinity, &prev_affinity)) + { + NCNN_LOGE("SetThreadGroupAffinity failed %d", GetLastError()); + return -1; + } + + return 0; + } + } + + NCNN_LOGE("No CPUs enabled in affinity mask"); + return -1; } #endif // defined _WIN32 @@ -1563,7 +1681,14 @@ static int set_sched_affinity(const ncnn::CpuSet& thread_affinity_mask) pid_t pid = syscall(SYS_gettid); #endif - int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(cpu_set_t), &thread_affinity_mask.cpu_set); + const cpu_set_t* cpuset = thread_affinity_mask.get_cpu_set(); + if (!cpuset) + { + NCNN_LOGE("Failed to get cpu_set from CpuSet"); + return -1; + } + + int syscallret = syscall(__NR_sched_setaffinity, pid, CPU_ALLOC_SIZE(CPU_SETSIZE), cpuset); if (syscallret) { NCNN_LOGE("syscall error %d", syscallret); @@ -1586,7 +1711,8 @@ static int set_sched_affinity(const ncnn::CpuSet& thread_affinity_mask) // see https://github.com/Tencent/ncnn/pull/2335#discussion_r528233919 --- AmeAkio int affinity_tag = THREAD_AFFINITY_TAG_NULL; - for (int i = 0; i < (int)sizeof(thread_affinity_mask.policy) * 8; i++) + int max_cpu = thread_affinity_mask.max_cpu_id(); + for (int i = 0; i <= max_cpu && i < 32; i++) // Apple policy is limited to 32 bits { if (thread_affinity_mask.is_enabled(i)) { @@ -2056,13 +2182,25 @@ static int get_sched_affinity(ncnn::CpuSet& thread_affinity_mask) thread_affinity_mask.disable_all(); - int syscallret = syscall(__NR_sched_getaffinity, pid, sizeof(cpu_set_t), &thread_affinity_mask.cpu_set); + // Allocate a temporary cpu_set_t for the syscall + cpu_set_t* temp_cpuset = CPU_ALLOC(CPU_SETSIZE); + if (!temp_cpuset) + { + return -1; + } + + int syscallret = syscall(__NR_sched_getaffinity, pid, CPU_ALLOC_SIZE(CPU_SETSIZE), temp_cpuset); if (syscallret) { + CPU_FREE(temp_cpuset); // handle get error silently return -1; } + // Copy the result to our CpuSet + thread_affinity_mask.set_cpu_set(temp_cpuset); + CPU_FREE(temp_cpuset); + return 0; } @@ -2153,6 +2291,10 @@ static void initialize_global_cpu_info() g_cpucount = get_cpucount(); g_physical_cpucount = get_physical_cpucount(); g_powersave = 0; + + // Set global max CPU count for CpuSet optimization + g_max_cpu_count = g_cpucount; + initialize_cpu_thread_affinity_mask(g_cpu_affinity_mask_all, g_cpu_affinity_mask_little, g_cpu_affinity_mask_big); #if (defined _WIN32 && (__aarch64__ || __arm__)) || ((defined __ANDROID__ || defined __linux__) && __riscv) @@ -2269,142 +2411,506 @@ static inline void try_initialize_global_cpu_info() namespace ncnn { -#if defined _WIN32 +// New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) +#if defined _WIN32 + , legacy_mask_cache(0) + , legacy_mask_valid(false) +#endif +#if defined __ANDROID__ || defined __linux__ + , cpu_set_cache(nullptr) + , cpu_set_valid(false) +#endif +#if __APPLE__ + , legacy_policy_cache(0) + , legacy_policy_valid(false) +#endif { - disable_all(); } -void CpuSet::enable(int cpu) +CpuSet::CpuSet(const CpuSet& other) + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) +#if defined _WIN32 + , legacy_mask_cache(0) + , legacy_mask_valid(false) +#endif +#if defined __ANDROID__ || defined __linux__ + , cpu_set_cache(nullptr) + , cpu_set_valid(false) +#endif +#if __APPLE__ + , legacy_policy_cache(0) + , legacy_policy_valid(false) +#endif { - mask |= ((ULONG_PTR)1 << cpu); + copy_from(other); } -void CpuSet::disable(int cpu) +CpuSet& CpuSet::operator=(const CpuSet& other) { - mask &= ~((ULONG_PTR)1 << cpu); + if (this != &other) + { + copy_from(other); + } + return *this; } -void CpuSet::disable_all() +CpuSet::~CpuSet() { - mask = 0; + if (extended_mask) + { + free(extended_mask); + } +#if defined __ANDROID__ || defined __linux__ + if (cpu_set_cache) + { + CPU_FREE(cpu_set_cache); + } +#endif } -bool CpuSet::is_enabled(int cpu) const +void CpuSet::copy_from(const CpuSet& other) { - return mask & ((ULONG_PTR)1 << cpu); -} + // Clean up existing state + if (extended_mask) + { + free(extended_mask); + extended_mask = nullptr; + } + extended_capacity = 0; -int CpuSet::num_enabled() const -{ - int num_enabled = 0; - for (int i = 0; i < (int)sizeof(mask) * 8; i++) + // Copy basic state + fast_mask = other.fast_mask; + use_extended = other.use_extended; + + // Copy extended mask if needed + if (other.use_extended && other.extended_mask) { - if (is_enabled(i)) - num_enabled++; + extended_capacity = other.extended_capacity; + extended_mask = (uint64_t*)malloc(extended_capacity * sizeof(uint64_t)); + if (extended_mask) + { + memcpy(extended_mask, other.extended_mask, extended_capacity * sizeof(uint64_t)); + } } - return num_enabled; + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; + if (cpu_set_cache) + { + CPU_FREE(cpu_set_cache); + cpu_set_cache = nullptr; + } +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } -#elif defined __ANDROID__ || defined __linux__ -CpuSet::CpuSet() + +void CpuSet::ensure_capacity(int cpu_id) { - disable_all(); -} + if (cpu_id < FAST_PATH_BITS && !use_extended) + { + return; // Fast path is sufficient + } + // Need to switch to extended mode + if (!use_extended) + { + use_extended = true; + // Calculate required capacity + int required_words = (cpu_id / BITS_PER_WORD) + 1; + extended_capacity = std::max(required_words, 2); // Minimum 2 words + extended_mask = (uint64_t*)calloc(extended_capacity, sizeof(uint64_t)); + if (extended_mask) + { + // Copy fast_mask to extended_mask[0] + extended_mask[0] = fast_mask; + } + return; + } + + // Already in extended mode, check if we need more capacity + int required_words = (cpu_id / BITS_PER_WORD) + 1; + if (required_words > extended_capacity) + { + int new_capacity = std::max(required_words, extended_capacity * 2); + uint64_t* new_mask = (uint64_t*)realloc(extended_mask, new_capacity * sizeof(uint64_t)); + if (new_mask) + { + // Zero out new memory + memset(new_mask + extended_capacity, 0, (new_capacity - extended_capacity) * sizeof(uint64_t)); + extended_mask = new_mask; + extended_capacity = new_capacity; + } + } +} void CpuSet::enable(int cpu) { - CPU_SET(cpu, &cpu_set); + if (cpu < 0) return; + + ensure_capacity(cpu); + + if (!use_extended && cpu < FAST_PATH_BITS) + { + fast_mask |= (1ULL << cpu); + } + else if (use_extended && extended_mask) + { + int word_idx = cpu / BITS_PER_WORD; + int bit_idx = cpu % BITS_PER_WORD; + if (word_idx < extended_capacity) + { + extended_mask[word_idx] |= (1ULL << bit_idx); + } + } + + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } void CpuSet::disable(int cpu) { - CPU_CLR(cpu, &cpu_set); + if (cpu < 0) return; + + if (!use_extended && cpu < FAST_PATH_BITS) + { + fast_mask &= ~(1ULL << cpu); + } + else if (use_extended && extended_mask) + { + int word_idx = cpu / BITS_PER_WORD; + int bit_idx = cpu % BITS_PER_WORD; + if (word_idx < extended_capacity) + { + extended_mask[word_idx] &= ~(1ULL << bit_idx); + } + } + + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } void CpuSet::disable_all() { - CPU_ZERO(&cpu_set); + fast_mask = 0; + if (use_extended && extended_mask) + { + memset(extended_mask, 0, extended_capacity * sizeof(uint64_t)); + } + + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } bool CpuSet::is_enabled(int cpu) const { - return CPU_ISSET(cpu, &cpu_set); -} + if (cpu < 0) return false; -int CpuSet::num_enabled() const -{ - int num_enabled = 0; - for (int i = 0; i < (int)sizeof(cpu_set_t) * 8; i++) + if (!use_extended && cpu < FAST_PATH_BITS) { - if (is_enabled(i)) - num_enabled++; + return (fast_mask & (1ULL << cpu)) != 0; + } + else if (use_extended && extended_mask) + { + int word_idx = cpu / BITS_PER_WORD; + int bit_idx = cpu % BITS_PER_WORD; + if (word_idx < extended_capacity) + { + return (extended_mask[word_idx] & (1ULL << bit_idx)) != 0; + } } - return num_enabled; + return false; } -#elif __APPLE__ -CpuSet::CpuSet() +// Helper function to count bits in a 64-bit integer +static int popcount64(uint64_t x) { - disable_all(); +#if defined(__GNUC__) || defined(__clang__) + return __builtin_popcountll(x); +#elif defined(_MSC_VER) + return (int)__popcnt64(x); +#else + // Fallback implementation + int count = 0; + while (x) + { + count += x & 1; + x >>= 1; + } + return count; +#endif } -void CpuSet::enable(int cpu) +int CpuSet::num_enabled() const { - policy |= ((unsigned int)1 << cpu); + int count = 0; + + if (!use_extended) + { + // Fast path: count bits in fast_mask + count = popcount64(fast_mask); + } + else if (extended_mask) + { + // Extended path: count bits in all words + for (int i = 0; i < extended_capacity; i++) + { + count += popcount64(extended_mask[i]); + } + } + + return count; } -void CpuSet::disable(int cpu) +int CpuSet::max_cpu_id() const { - policy &= ~((unsigned int)1 << cpu); + if (!use_extended) + { + if (fast_mask == 0) return -1; + + // Find highest set bit in fast_mask + for (int i = FAST_PATH_BITS - 1; i >= 0; i--) + { + if (fast_mask & (1ULL << i)) + return i; + } + return -1; + } + else if (extended_mask) + { + // Find highest set bit in extended_mask + for (int word = extended_capacity - 1; word >= 0; word--) + { + if (extended_mask[word] != 0) + { + for (int bit = BITS_PER_WORD - 1; bit >= 0; bit--) + { + if (extended_mask[word] & (1ULL << bit)) + return word * BITS_PER_WORD + bit; + } + } + } + } + + return -1; } -void CpuSet::disable_all() +bool CpuSet::is_empty() const { - policy = 0; + if (!use_extended) + { + return fast_mask == 0; + } + else if (extended_mask) + { + for (int i = 0; i < extended_capacity; i++) + { + if (extended_mask[i] != 0) + return false; + } + } + + return true; } -bool CpuSet::is_enabled(int cpu) const +void CpuSet::set_range(int start_cpu, int end_cpu, bool enabled) { - return policy & ((unsigned int)1 << cpu); -} + if (start_cpu < 0 || end_cpu < start_cpu) return; -int CpuSet::num_enabled() const + for (int cpu = start_cpu; cpu <= end_cpu; cpu++) + { + if (enabled) + enable(cpu); + else + disable(cpu); + } +} +// Platform-specific compatibility methods +#if defined _WIN32 +ULONG_PTR CpuSet::get_legacy_mask() const { - int num_enabled = 0; - for (int i = 0; i < (int)sizeof(policy) * 8; i++) + if (!legacy_mask_valid) { - if (is_enabled(i)) - num_enabled++; + legacy_mask_cache = 0; + + if (!use_extended) + { + // Fast path: directly use fast_mask (truncated to ULONG_PTR size) + legacy_mask_cache = (ULONG_PTR)(fast_mask & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + } + else if (extended_mask && extended_capacity > 0) + { + // Extended path: use first word, truncated to ULONG_PTR size + legacy_mask_cache = (ULONG_PTR)(extended_mask[0] & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + } + + legacy_mask_valid = true; } - return num_enabled; + return legacy_mask_cache; } -#else -CpuSet::CpuSet() + +void CpuSet::set_legacy_mask(ULONG_PTR mask) { + disable_all(); + + // Set bits according to the legacy mask + for (int i = 0; i < (int)(sizeof(ULONG_PTR) * 8); i++) + { + if (mask & ((ULONG_PTR)1 << i)) + { + enable(i); + } + } } +#endif -void CpuSet::enable(int /* cpu */) +#if defined __ANDROID__ || defined __linux__ +const cpu_set_t* CpuSet::get_cpu_set() const { + if (!cpu_set_valid) + { + // Allocate cpu_set_t if not already done + if (!cpu_set_cache) + { + cpu_set_cache = CPU_ALLOC(CPU_SETSIZE); + if (!cpu_set_cache) + return nullptr; + } + + CPU_ZERO_S(CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); + + // Copy our internal representation to cpu_set_t + if (!use_extended) + { + for (int i = 0; i < FAST_PATH_BITS && i < CPU_SETSIZE; i++) + { + if (fast_mask & (1ULL << i)) + { + CPU_SET_S(i, CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); + } + } + } + else if (extended_mask) + { + for (int word = 0; word < extended_capacity; word++) + { + uint64_t mask = extended_mask[word]; + for (int bit = 0; bit < BITS_PER_WORD; bit++) + { + int cpu_id = word * BITS_PER_WORD + bit; + if (cpu_id >= CPU_SETSIZE) break; + + if (mask & (1ULL << bit)) + { + CPU_SET_S(cpu_id, CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); + } + } + if ((word + 1) * BITS_PER_WORD >= CPU_SETSIZE) break; + } + } + + cpu_set_valid = true; + } + + return cpu_set_cache; } -void CpuSet::disable(int /* cpu */) +cpu_set_t* CpuSet::get_cpu_set_mutable() { + get_cpu_set(); // Ensure cache is valid + return cpu_set_cache; } -void CpuSet::disable_all() +void CpuSet::set_cpu_set(const cpu_set_t* cpuset) { + if (!cpuset) return; + + disable_all(); + + // Copy from cpu_set_t to our internal representation + for (int i = 0; i < CPU_SETSIZE; i++) + { + if (CPU_ISSET(i, cpuset)) + { + enable(i); + } + } } +#endif -bool CpuSet::is_enabled(int /* cpu */) const +#if __APPLE__ +unsigned int CpuSet::get_legacy_policy() const { - return true; + if (!legacy_policy_valid) + { + legacy_policy_cache = 0; + + if (!use_extended) + { + // Fast path: directly use fast_mask (truncated to 32 bits) + legacy_policy_cache = (unsigned int)(fast_mask & 0xFFFFFFFFU); + } + else if (extended_mask && extended_capacity > 0) + { + // Extended path: use first word, truncated to 32 bits + legacy_policy_cache = (unsigned int)(extended_mask[0] & 0xFFFFFFFFU); + } + + legacy_policy_valid = true; + } + + return legacy_policy_cache; } -int CpuSet::num_enabled() const +void CpuSet::set_legacy_policy(unsigned int policy) { - return get_cpu_count(); + disable_all(); + + // Set bits according to the legacy policy + for (int i = 0; i < 32; i++) + { + if (policy & (1U << i)) + { + enable(i); + } + } } #endif @@ -3069,7 +3575,8 @@ int set_cpu_thread_affinity(const CpuSet& thread_affinity_mask) { // assign one core for each thread int core = -1 - i; - for (int j = 0; j < (int)sizeof(thread_affinity_mask.policy) * 8; j++) + int max_cpu = thread_affinity_mask.max_cpu_id(); + for (int j = 0; j <= max_cpu && j < 32; j++) // Apple policy is limited to 32 bits { if (thread_affinity_mask.is_enabled(j)) { diff --git a/src/cpu.h b/src/cpu.h index 6a3fcea29..1a13636bc 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -1,16 +1,5 @@ -// Tencent is pleased to support the open source community by making ncnn available. -// -// Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. -// -// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except -// in compliance with the License. You may obtain a copy of the License at -// -// https://opensource.org/licenses/BSD-3-Clause -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. +// Copyright 2017 Tencent +// SPDX-License-Identifier: BSD-3-Clause #ifndef NCNN_CPU_H #define NCNN_CPU_H @@ -33,21 +22,64 @@ class NCNN_EXPORT CpuSet { public: CpuSet(); + CpuSet(const CpuSet& other); + CpuSet& operator=(const CpuSet& other); + ~CpuSet(); + void enable(int cpu); void disable(int cpu); void disable_all(); bool is_enabled(int cpu) const; int num_enabled() const; -public: + // New methods for >64 CPU support + int max_cpu_id() const; + bool is_empty() const; + void set_range(int start_cpu, int end_cpu, bool enabled); + + // Platform-specific accessors for backward compatibility +#if defined _WIN32 + ULONG_PTR get_legacy_mask() const; + void set_legacy_mask(ULONG_PTR mask); +#endif +#if defined __ANDROID__ || defined __linux__ + const cpu_set_t* get_cpu_set() const; + cpu_set_t* get_cpu_set_mutable(); + void set_cpu_set(const cpu_set_t* cpuset); +#endif +#if __APPLE__ + unsigned int get_legacy_policy() const; + void set_legacy_policy(unsigned int policy); +#endif + +private: + void ensure_capacity(int cpu_id); + void copy_from(const CpuSet& other); + + // Internal implementation details + static const int FAST_PATH_BITS = 64; + static const int BITS_PER_WORD = 64; + + // Fast path for systems with <= 64 CPUs + uint64_t fast_mask; + + // Extended path for systems with > 64 CPUs + uint64_t* extended_mask; + int extended_capacity; // in number of uint64_t words + bool use_extended; + + // Platform-specific storage for compatibility #if defined _WIN32 - ULONG_PTR mask; + mutable ULONG_PTR legacy_mask_cache; + mutable bool legacy_mask_valid; #endif #if defined __ANDROID__ || defined __linux__ - cpu_set_t cpu_set; + mutable cpu_set_t* cpu_set_cache; + mutable bool cpu_set_valid; #endif #if __APPLE__ - unsigned int policy; + mutable unsigned int legacy_policy_cache; + mutable bool legacy_policy_valid; #endif }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9d5b6517e..e94c52b87 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,9 +60,16 @@ endif() ncnn_add_test(c_api) ncnn_add_test(cpu) +ncnn_add_test(cpu_large) +ncnn_add_test(cpu_simulation) ncnn_add_test(expression) ncnn_add_test(paramdict) +# Add validate_cpu_support test manually +add_executable(test_validate_cpu_support validate_cpu_support.cpp) +target_link_libraries(test_validate_cpu_support ncnn) +add_test(NAME test_validate_cpu_support COMMAND test_validate_cpu_support) + if(NCNN_VULKAN) ncnn_add_test(command) endif() diff --git a/tests/test_cpu_large.cpp b/tests/test_cpu_large.cpp new file mode 100644 index 000000000..749177864 --- /dev/null +++ b/tests/test_cpu_large.cpp @@ -0,0 +1,250 @@ +// Copyright 2024 Tencent +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include + +#include "cpu.h" + +// Test CpuSet with >64 CPUs +static int test_cpuset_large() +{ + printf("Testing CpuSet with >64 CPUs...\n"); + + ncnn::CpuSet set; + + // Test basic operations with large CPU IDs + const int test_cpus[] = {0, 63, 64, 65, 127, 128, 255, 256, 511, 512, 1023}; + const int num_test_cpus = sizeof(test_cpus) / sizeof(test_cpus[0]); + + // Initially all should be disabled + for (int i = 0; i < num_test_cpus; i++) + { + if (set.is_enabled(test_cpus[i])) + { + fprintf(stderr, "CPU %d should be disabled initially\n", test_cpus[i]); + return 1; + } + } + + if (set.num_enabled() != 0) + { + fprintf(stderr, "Initially no CPUs should be enabled\n"); + return 1; + } + + if (!set.is_empty()) + { + fprintf(stderr, "Initially CpuSet should be empty\n"); + return 1; + } + + // Enable all test CPUs + for (int i = 0; i < num_test_cpus; i++) + { + set.enable(test_cpus[i]); + } + + // Verify they are enabled + for (int i = 0; i < num_test_cpus; i++) + { + if (!set.is_enabled(test_cpus[i])) + { + fprintf(stderr, "CPU %d should be enabled\n", test_cpus[i]); + return 1; + } + } + + if (set.num_enabled() != num_test_cpus) + { + fprintf(stderr, "Expected %d enabled CPUs, got %d\n", num_test_cpus, set.num_enabled()); + return 1; + } + + if (set.is_empty()) + { + fprintf(stderr, "CpuSet should not be empty after enabling CPUs\n"); + return 1; + } + + // Test max_cpu_id + int max_cpu = set.max_cpu_id(); + if (max_cpu != 1023) + { + fprintf(stderr, "Expected max CPU ID 1023, got %d\n", max_cpu); + return 1; + } + + // Test disable + set.disable(test_cpus[0]); + if (set.is_enabled(test_cpus[0])) + { + fprintf(stderr, "CPU %d should be disabled after disable()\n", test_cpus[0]); + return 1; + } + + if (set.num_enabled() != num_test_cpus - 1) + { + fprintf(stderr, "Expected %d enabled CPUs after disable, got %d\n", + num_test_cpus - 1, set.num_enabled()); + return 1; + } + + // Test set_range + set.disable_all(); + set.set_range(100, 200, true); + + int expected_range_count = 200 - 100 + 1; + if (set.num_enabled() != expected_range_count) + { + fprintf(stderr, "Expected %d CPUs in range [100,200], got %d\n", + expected_range_count, set.num_enabled()); + return 1; + } + + for (int i = 100; i <= 200; i++) + { + if (!set.is_enabled(i)) + { + fprintf(stderr, "CPU %d should be enabled in range [100,200]\n", i); + return 1; + } + } + + // Test copy constructor + ncnn::CpuSet set_copy(set); + if (set_copy.num_enabled() != set.num_enabled()) + { + fprintf(stderr, "Copy constructor failed: different num_enabled\n"); + return 1; + } + + for (int i = 0; i <= 1023; i++) + { + if (set_copy.is_enabled(i) != set.is_enabled(i)) + { + fprintf(stderr, "Copy constructor failed: CPU %d state differs\n", i); + return 1; + } + } + + // Test assignment operator + ncnn::CpuSet set_assigned; + set_assigned.enable(999); + set_assigned = set; + + if (set_assigned.num_enabled() != set.num_enabled()) + { + fprintf(stderr, "Assignment operator failed: different num_enabled\n"); + return 1; + } + + for (int i = 0; i <= 1023; i++) + { + if (set_assigned.is_enabled(i) != set.is_enabled(i)) + { + fprintf(stderr, "Assignment operator failed: CPU %d state differs\n", i); + return 1; + } + } + + printf("CpuSet large CPU test passed!\n"); + return 0; +} + +// Test boundary conditions +static int test_cpuset_boundary() +{ + printf("Testing CpuSet boundary conditions...\n"); + + ncnn::CpuSet set; + + // Test CPU ID 0 + set.enable(0); + if (!set.is_enabled(0)) + { + fprintf(stderr, "CPU 0 should be enabled\n"); + return 1; + } + + // Test exactly 64 CPUs (boundary between fast and extended path) + set.disable_all(); + for (int i = 0; i < 64; i++) + { + set.enable(i); + } + + if (set.num_enabled() != 64) + { + fprintf(stderr, "Expected 64 enabled CPUs, got %d\n", set.num_enabled()); + return 1; + } + + // Test 65th CPU (should trigger extended mode) + set.enable(64); + if (set.num_enabled() != 65) + { + fprintf(stderr, "Expected 65 enabled CPUs, got %d\n", set.num_enabled()); + return 1; + } + + // Test negative CPU ID (should be ignored) + set.enable(-1); + set.disable(-1); + // Should not crash + + // Test very large CPU ID + set.enable(10000); + if (!set.is_enabled(10000)) + { + fprintf(stderr, "CPU 10000 should be enabled\n"); + return 1; + } + + printf("CpuSet boundary test passed!\n"); + return 0; +} + +// Test performance with large CPU sets +static int test_cpuset_performance() +{ + printf("Testing CpuSet performance with large CPU sets...\n"); + + ncnn::CpuSet set; + + // Enable many CPUs + const int max_cpu = 2048; + for (int i = 0; i < max_cpu; i += 2) // Enable every other CPU + { + set.enable(i); + } + + // Verify count + int expected_count = max_cpu / 2; + if (set.num_enabled() != expected_count) + { + fprintf(stderr, "Expected %d enabled CPUs, got %d\n", expected_count, set.num_enabled()); + return 1; + } + + // Test copy performance + ncnn::CpuSet set_copy(set); + if (set_copy.num_enabled() != expected_count) + { + fprintf(stderr, "Copy failed: expected %d enabled CPUs, got %d\n", + expected_count, set_copy.num_enabled()); + return 1; + } + + printf("CpuSet performance test passed!\n"); + return 0; +} + +int main() +{ + return 0 + || test_cpuset_large() + || test_cpuset_boundary() + || test_cpuset_performance(); +} From 6654b8ff70a84a43d3af004ac4eb613596813131 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Sun, 13 Jul 2025 21:58:26 +0800 Subject: [PATCH 02/26] Support for >64 CPU systems --- 64 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 64 diff --git a/64 b/64 new file mode 100644 index 000000000..e69de29bb From 601c8c20c8ebbf57d1380f637c0400cf0d5849ee Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Tue, 15 Jul 2025 11:21:14 +0800 Subject: [PATCH 03/26] Support for >64 CPU systems in NCNN.Fix CMakeLists error. --- tests/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e94c52b87..b5f4634ca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -61,15 +61,9 @@ endif() ncnn_add_test(c_api) ncnn_add_test(cpu) ncnn_add_test(cpu_large) -ncnn_add_test(cpu_simulation) ncnn_add_test(expression) ncnn_add_test(paramdict) -# Add validate_cpu_support test manually -add_executable(test_validate_cpu_support validate_cpu_support.cpp) -target_link_libraries(test_validate_cpu_support ncnn) -add_test(NAME test_validate_cpu_support COMMAND test_validate_cpu_support) - if(NCNN_VULKAN) ncnn_add_test(command) endif() From f7937bd5ddbf1324029d8f396498beea2ad65d8f Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Wed, 16 Jul 2025 21:38:27 +0800 Subject: [PATCH 04/26] Fix uint64_t compilation errors and implement >64 CPU support - Add #include to cpu.h, cpu.cpp, and platform.h.in - Implement extended CpuSet class supporting >64 CPUs - Add fast path for <=64 CPUs and extended path for >64 CPUs - Include necessary headers for std::max, std::vector, memset, etc. - Fix original code's missing stdint.h includes for uint64_t usage - Maintain backward compatibility with platform-specific APIs Fixes #6142 --- src/cpu.cpp | 1 + src/cpu.h | 1 + src/platform.h.in | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/cpu.cpp b/src/cpu.cpp index ba806e3b7..086c5c8e5 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -5,6 +5,7 @@ #include "platform.h" +#include #include #ifndef __wasi__ #include diff --git a/src/cpu.h b/src/cpu.h index 1a13636bc..8d77b2736 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -5,6 +5,7 @@ #define NCNN_CPU_H #include +#include #if defined _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/src/platform.h.in b/src/platform.h.in index 50e80e648..54270bce3 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -15,6 +15,8 @@ #ifndef NCNN_PLATFORM_H #define NCNN_PLATFORM_H +#include + #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING #cmakedefine01 NCNN_SIMPLEOCV From d2ccaf9ca76ba1193956248320a4bb7a27954a9a Mon Sep 17 00:00:00 2001 From: yok7 <145077166+yok7@users.noreply.github.com> Date: Wed, 16 Jul 2025 14:10:47 +0000 Subject: [PATCH 05/26] apply code-format changes --- src/cpu.cpp | 40 ++++++++++----------- tests/test_cpu_large.cpp | 76 ++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 086c5c8e5..2cb1a89af 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -2414,41 +2414,41 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { copy_from(other); diff --git a/tests/test_cpu_large.cpp b/tests/test_cpu_large.cpp index 749177864..3dcebd7b5 100644 --- a/tests/test_cpu_large.cpp +++ b/tests/test_cpu_large.cpp @@ -11,13 +11,13 @@ static int test_cpuset_large() { printf("Testing CpuSet with >64 CPUs...\n"); - + ncnn::CpuSet set; - + // Test basic operations with large CPU IDs const int test_cpus[] = {0, 63, 64, 65, 127, 128, 255, 256, 511, 512, 1023}; const int num_test_cpus = sizeof(test_cpus) / sizeof(test_cpus[0]); - + // Initially all should be disabled for (int i = 0; i < num_test_cpus; i++) { @@ -27,25 +27,25 @@ static int test_cpuset_large() return 1; } } - + if (set.num_enabled() != 0) { fprintf(stderr, "Initially no CPUs should be enabled\n"); return 1; } - + if (!set.is_empty()) { fprintf(stderr, "Initially CpuSet should be empty\n"); return 1; } - + // Enable all test CPUs for (int i = 0; i < num_test_cpus; i++) { set.enable(test_cpus[i]); } - + // Verify they are enabled for (int i = 0; i < num_test_cpus; i++) { @@ -55,19 +55,19 @@ static int test_cpuset_large() return 1; } } - + if (set.num_enabled() != num_test_cpus) { fprintf(stderr, "Expected %d enabled CPUs, got %d\n", num_test_cpus, set.num_enabled()); return 1; } - + if (set.is_empty()) { fprintf(stderr, "CpuSet should not be empty after enabling CPUs\n"); return 1; } - + // Test max_cpu_id int max_cpu = set.max_cpu_id(); if (max_cpu != 1023) @@ -75,7 +75,7 @@ static int test_cpuset_large() fprintf(stderr, "Expected max CPU ID 1023, got %d\n", max_cpu); return 1; } - + // Test disable set.disable(test_cpus[0]); if (set.is_enabled(test_cpus[0])) @@ -83,26 +83,26 @@ static int test_cpuset_large() fprintf(stderr, "CPU %d should be disabled after disable()\n", test_cpus[0]); return 1; } - + if (set.num_enabled() != num_test_cpus - 1) { - fprintf(stderr, "Expected %d enabled CPUs after disable, got %d\n", + fprintf(stderr, "Expected %d enabled CPUs after disable, got %d\n", num_test_cpus - 1, set.num_enabled()); return 1; } - + // Test set_range set.disable_all(); set.set_range(100, 200, true); - + int expected_range_count = 200 - 100 + 1; if (set.num_enabled() != expected_range_count) { - fprintf(stderr, "Expected %d CPUs in range [100,200], got %d\n", + fprintf(stderr, "Expected %d CPUs in range [100,200], got %d\n", expected_range_count, set.num_enabled()); return 1; } - + for (int i = 100; i <= 200; i++) { if (!set.is_enabled(i)) @@ -111,7 +111,7 @@ static int test_cpuset_large() return 1; } } - + // Test copy constructor ncnn::CpuSet set_copy(set); if (set_copy.num_enabled() != set.num_enabled()) @@ -119,7 +119,7 @@ static int test_cpuset_large() fprintf(stderr, "Copy constructor failed: different num_enabled\n"); return 1; } - + for (int i = 0; i <= 1023; i++) { if (set_copy.is_enabled(i) != set.is_enabled(i)) @@ -128,18 +128,18 @@ static int test_cpuset_large() return 1; } } - + // Test assignment operator ncnn::CpuSet set_assigned; set_assigned.enable(999); set_assigned = set; - + if (set_assigned.num_enabled() != set.num_enabled()) { fprintf(stderr, "Assignment operator failed: different num_enabled\n"); return 1; } - + for (int i = 0; i <= 1023; i++) { if (set_assigned.is_enabled(i) != set.is_enabled(i)) @@ -148,7 +148,7 @@ static int test_cpuset_large() return 1; } } - + printf("CpuSet large CPU test passed!\n"); return 0; } @@ -157,9 +157,9 @@ static int test_cpuset_large() static int test_cpuset_boundary() { printf("Testing CpuSet boundary conditions...\n"); - + ncnn::CpuSet set; - + // Test CPU ID 0 set.enable(0); if (!set.is_enabled(0)) @@ -167,20 +167,20 @@ static int test_cpuset_boundary() fprintf(stderr, "CPU 0 should be enabled\n"); return 1; } - + // Test exactly 64 CPUs (boundary between fast and extended path) set.disable_all(); for (int i = 0; i < 64; i++) { set.enable(i); } - + if (set.num_enabled() != 64) { fprintf(stderr, "Expected 64 enabled CPUs, got %d\n", set.num_enabled()); return 1; } - + // Test 65th CPU (should trigger extended mode) set.enable(64); if (set.num_enabled() != 65) @@ -188,12 +188,12 @@ static int test_cpuset_boundary() fprintf(stderr, "Expected 65 enabled CPUs, got %d\n", set.num_enabled()); return 1; } - + // Test negative CPU ID (should be ignored) set.enable(-1); set.disable(-1); // Should not crash - + // Test very large CPU ID set.enable(10000); if (!set.is_enabled(10000)) @@ -201,7 +201,7 @@ static int test_cpuset_boundary() fprintf(stderr, "CPU 10000 should be enabled\n"); return 1; } - + printf("CpuSet boundary test passed!\n"); return 0; } @@ -210,16 +210,16 @@ static int test_cpuset_boundary() static int test_cpuset_performance() { printf("Testing CpuSet performance with large CPU sets...\n"); - + ncnn::CpuSet set; - + // Enable many CPUs const int max_cpu = 2048; - for (int i = 0; i < max_cpu; i += 2) // Enable every other CPU + for (int i = 0; i < max_cpu; i += 2) // Enable every other CPU { set.enable(i); } - + // Verify count int expected_count = max_cpu / 2; if (set.num_enabled() != expected_count) @@ -227,16 +227,16 @@ static int test_cpuset_performance() fprintf(stderr, "Expected %d enabled CPUs, got %d\n", expected_count, set.num_enabled()); return 1; } - + // Test copy performance ncnn::CpuSet set_copy(set); if (set_copy.num_enabled() != expected_count) { - fprintf(stderr, "Copy failed: expected %d enabled CPUs, got %d\n", + fprintf(stderr, "Copy failed: expected %d enabled CPUs, got %d\n", expected_count, set_copy.num_enabled()); return 1; } - + printf("CpuSet performance test passed!\n"); return 0; } From 5c01e05633de5e9db65f66f559cc16e916f9593c Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 10:26:34 +0800 Subject: [PATCH 06/26] Add missing header for std::pair usage - Fix compilation error for std::pair usage in Windows processor detection - std::pair requires header to be explicitly included - Ensures compatibility across different compilers and environments --- src/cpu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cpu.cpp b/src/cpu.cpp index 2cb1a89af..c740e4397 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #ifdef _OPENMP From 81f1aa9dff4244ffe08a14fd421512dcbb173983 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 14:52:23 +0800 Subject: [PATCH 07/26] Fix NCNN_SIMPLESTL compatibility and improve bit shift safety - Add conditional header includes for uint64_t in all build modes - Include in SIMPLESTL mode, in normal mode - Move standard library headers to conditional compilation blocks - Fix unsafe bit shift operations that could cause undefined behavior - Ensure >64 CPU support works correctly in both SIMPLESTL and normal modes - Tested successfully in NCNN_SIMPLESTL=ON mode --- src/cpu.cpp | 67 +++++++++++++++++++++++++++++++---------------- src/cpu.h | 1 - src/platform.h.in | 5 ++++ 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index c740e4397..23edbf5e5 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -5,7 +5,6 @@ #include "platform.h" -#include #include #ifndef __wasi__ #include @@ -14,9 +13,13 @@ #include #include #include + +#if !NCNN_SIMPLESTL #include +#include #include #include +#endif #ifdef _OPENMP #if NCNN_SIMPLEOMP @@ -2415,41 +2418,41 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) #if defined _WIN32 - , - legacy_mask_cache(0), - legacy_mask_valid(false) + , legacy_mask_cache(0) + , legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , - cpu_set_cache(nullptr), - cpu_set_valid(false) + , cpu_set_cache(nullptr) + , cpu_set_valid(false) #endif #if __APPLE__ - , - legacy_policy_cache(0), - legacy_policy_valid(false) + , legacy_policy_cache(0) + , legacy_policy_valid(false) #endif { } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) #if defined _WIN32 - , - legacy_mask_cache(0), - legacy_mask_valid(false) + , legacy_mask_cache(0) + , legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , - cpu_set_cache(nullptr), - cpu_set_valid(false) + , cpu_set_cache(nullptr) + , cpu_set_valid(false) #endif #if __APPLE__ - , - legacy_policy_cache(0), - legacy_policy_valid(false) + , legacy_policy_cache(0) + , legacy_policy_valid(false) #endif { copy_from(other); @@ -2774,12 +2777,30 @@ ULONG_PTR CpuSet::get_legacy_mask() const if (!use_extended) { // Fast path: directly use fast_mask (truncated to ULONG_PTR size) - legacy_mask_cache = (ULONG_PTR)(fast_mask & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + if (sizeof(ULONG_PTR) >= sizeof(uint64_t)) + { + legacy_mask_cache = (ULONG_PTR)fast_mask; + } + else + { + // Create mask for ULONG_PTR size without undefined behavior + const uint64_t ptr_mask = (sizeof(ULONG_PTR) == 4) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + legacy_mask_cache = (ULONG_PTR)(fast_mask & ptr_mask); + } } else if (extended_mask && extended_capacity > 0) { // Extended path: use first word, truncated to ULONG_PTR size - legacy_mask_cache = (ULONG_PTR)(extended_mask[0] & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + if (sizeof(ULONG_PTR) >= sizeof(uint64_t)) + { + legacy_mask_cache = (ULONG_PTR)extended_mask[0]; + } + else + { + // Create mask for ULONG_PTR size without undefined behavior + const uint64_t ptr_mask = (sizeof(ULONG_PTR) == 4) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + legacy_mask_cache = (ULONG_PTR)(extended_mask[0] & ptr_mask); + } } legacy_mask_valid = true; diff --git a/src/cpu.h b/src/cpu.h index 8d77b2736..1a13636bc 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -5,7 +5,6 @@ #define NCNN_CPU_H #include -#include #if defined _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/src/platform.h.in b/src/platform.h.in index 54270bce3..9ef23b990 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -15,7 +15,12 @@ #ifndef NCNN_PLATFORM_H #define NCNN_PLATFORM_H +// Ensure basic integer types are available in all modes +#if NCNN_SIMPLESTL +#include +#else #include +#endif #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING From 6a6dc1983829782d893b2776c76b58ba8feebe2d Mon Sep 17 00:00:00 2001 From: yok7 <145077166+yok7@users.noreply.github.com> Date: Thu, 17 Jul 2025 06:54:20 +0000 Subject: [PATCH 08/26] apply code-format changes --- src/cpu.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 23edbf5e5..2eb801c32 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -2418,41 +2418,41 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { copy_from(other); From 2f523eab9de5b8e1c26543048fe11c66ec8acac9 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 20:21:31 +0800 Subject: [PATCH 09/26] Fix Windows ARM compatibility for popcount64 function - Add architecture-specific conditional compilation for __popcnt64 - __popcnt64 is only available on x86/x64, not on ARM architectures - Use fallback implementation for ARM and other non-x86 architectures - Resolves LNK2019 unresolved external symbol error on Windows ARM builds - Maintains performance on x86/x64 while ensuring compatibility across all platforms --- src/cpu.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 23edbf5e5..8edc27833 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -2668,10 +2668,11 @@ static int popcount64(uint64_t x) { #if defined(__GNUC__) || defined(__clang__) return __builtin_popcountll(x); -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + // __popcnt64 is only available on x86/x64, not on ARM return (int)__popcnt64(x); #else - // Fallback implementation + // Fallback implementation for ARM and other architectures int count = 0; while (x) { From 6f9b6748b037bcbd30dac1e0f50664d574855163 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 20:53:54 +0800 Subject: [PATCH 10/26] Fix compilation and test issues - Fix C++03 compatibility by using instead of - Fix get_big_cpu_count() to return 0 when no big cores detected - Resolves multiheadattention test failures caused by thread count changes - Ensures compatibility with simplestl-simplemath mode --- src/cpu.cpp | 3 +-- src/platform.h.in | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 8edc27833..1dcf5f942 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -3475,8 +3475,7 @@ int get_little_cpu_count() int get_big_cpu_count() { try_initialize_global_cpu_info(); - int big_cpu_count = get_cpu_thread_affinity_mask(2).num_enabled(); - return big_cpu_count ? big_cpu_count : g_cpucount; + return get_cpu_thread_affinity_mask(2).num_enabled(); } int get_physical_cpu_count() diff --git a/src/platform.h.in b/src/platform.h.in index 9ef23b990..207502827 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -16,11 +16,8 @@ #define NCNN_PLATFORM_H // Ensure basic integer types are available in all modes -#if NCNN_SIMPLESTL +// Use C header for compatibility with C++03 and simple modes #include -#else -#include -#endif #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING From 5588f3bfc9cca37d45c93d8749f6c9db2cdcab78 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 21:04:36 +0800 Subject: [PATCH 11/26] Fix aarch64-native simplestl-simplemath compilation - Use stdint.h consistently for all modes to avoid C++03/C++11 conflicts - Prevents vector template conflicts between standard library and simplestl - Resolves 'wrong number of template arguments' errors in aarch64-native CI --- src/platform.h.in | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform.h.in b/src/platform.h.in index 207502827..1a3e32422 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -15,9 +15,7 @@ #ifndef NCNN_PLATFORM_H #define NCNN_PLATFORM_H -// Ensure basic integer types are available in all modes -// Use C header for compatibility with C++03 and simple modes -#include + #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING @@ -275,6 +273,9 @@ static inline void swap_endianness_32(void* x) #if NCNN_SIMPLESTL #include "simplestl.h" #else +// Ensure basic integer types are available when not using simplestl +// Use C header for compatibility with C++03 and simple modes +#include #include #include #include From 3d563cd3068246de72b81367ac792fff25e17647 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Wed, 23 Jul 2025 14:15:03 +0800 Subject: [PATCH 12/26] Fix popcount64 linking issue and improve compatibility - Fix undefined reference to __popcountdi2 by adding __POPCNT__ check - Use Brian Kernighan's algorithm for better fallback performance - Improve C compatibility by using NULL instead of nullptr - Use stdint.h instead of cstdint for better C compatibility - Prioritize MSVC __popcnt64 over GCC builtin for better reliability This resolves linking errors in environments where compiler builtins are not properly linked, particularly affecting test compilation. --- src/cpu.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 104a5d541..b55dcae2a 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -16,7 +16,7 @@ #if !NCNN_SIMPLESTL #include -#include +#include #include #include #endif @@ -1767,7 +1767,7 @@ static void initialize_cpu_thread_affinity_mask(ncnn::CpuSet& mask_all, ncnn::Cp if (glpie != NULL) { DWORD bufferSize = 0; - glpie(RelationProcessorCore, nullptr, &bufferSize); + glpie(RelationProcessorCore, NULL, &bufferSize); std::vector buffer(bufferSize); if (!GetLogicalProcessorInformationEx(RelationProcessorCore, (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(buffer.data()), &bufferSize)) @@ -2418,7 +2418,7 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0), extended_mask(NULL), extended_capacity(0), use_extended(false) #if defined _WIN32 , legacy_mask_cache(0), @@ -2426,7 +2426,7 @@ CpuSet::CpuSet() #endif #if defined __ANDROID__ || defined __linux__ , - cpu_set_cache(nullptr), + cpu_set_cache(NULL), cpu_set_valid(false) #endif #if __APPLE__ @@ -2438,7 +2438,7 @@ CpuSet::CpuSet() } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0), extended_mask(NULL), extended_capacity(0), use_extended(false) #if defined _WIN32 , legacy_mask_cache(0), @@ -2446,7 +2446,7 @@ CpuSet::CpuSet(const CpuSet& other) #endif #if defined __ANDROID__ || defined __linux__ , - cpu_set_cache(nullptr), + cpu_set_cache(NULL), cpu_set_valid(false) #endif #if __APPLE__ @@ -2487,7 +2487,7 @@ void CpuSet::copy_from(const CpuSet& other) if (extended_mask) { free(extended_mask); - extended_mask = nullptr; + extended_mask = NULL; } extended_capacity = 0; @@ -2515,7 +2515,7 @@ void CpuSet::copy_from(const CpuSet& other) if (cpu_set_cache) { CPU_FREE(cpu_set_cache); - cpu_set_cache = nullptr; + cpu_set_cache = NULL; } #endif #if __APPLE__ @@ -2666,18 +2666,20 @@ bool CpuSet::is_enabled(int cpu) const // Helper function to count bits in a 64-bit integer static int popcount64(uint64_t x) { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_popcountll(x); -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) // __popcnt64 is only available on x86/x64, not on ARM return (int)__popcnt64(x); +#elif (defined(__GNUC__) || defined(__clang__)) && defined(__POPCNT__) && !defined(__FREESTANDING__) && !NCNN_SIMPLESTL + // Only use builtin if POPCNT instruction is available + return __builtin_popcountll(x); #else - // Fallback implementation for ARM and other architectures + // Fallback implementation for compatibility + // Use Brian Kernighan's algorithm for better performance int count = 0; while (x) { - count += x & 1; - x >>= 1; + x &= x - 1; // Clear the lowest set bit + count++; } return count; #endif @@ -2835,7 +2837,7 @@ const cpu_set_t* CpuSet::get_cpu_set() const { cpu_set_cache = CPU_ALLOC(CPU_SETSIZE); if (!cpu_set_cache) - return nullptr; + return NULL; } CPU_ZERO_S(CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); From c475868e267aad4e73833f5bd604ced8971c7124 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Sun, 13 Jul 2025 21:57:06 +0800 Subject: [PATCH 13/26] Support for >64 CPU systems --- src/cpu.cpp | 726 +++++++++++++++++++++++++++++++++------ src/cpu.h | 51 ++- tests/CMakeLists.txt | 7 + tests/test_cpu_large.cpp | 250 ++++++++++++++ 4 files changed, 926 insertions(+), 108 deletions(-) create mode 100644 tests/test_cpu_large.cpp diff --git a/src/cpu.cpp b/src/cpu.cpp index a095b6b6f..8ed93c409 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #ifdef _OPENMP #if NCNN_SIMPLEOMP @@ -182,6 +184,7 @@ __attribute__((constructor)) void ncnn_kmp_env_initializer() static int g_cpucount; static int g_physical_cpucount; static int g_powersave; +static int g_max_cpu_count = 0; // Maximum CPU count detected at runtime static ncnn::CpuSet g_cpu_affinity_mask_all; static ncnn::CpuSet g_cpu_affinity_mask_little; static ncnn::CpuSet g_cpu_affinity_mask_big; @@ -916,24 +919,58 @@ static int get_cpucount() } #if defined __ANDROID__ || defined __linux__ -static int get_thread_siblings(int cpuid) +static void get_thread_siblings(int cpuid, ncnn::CpuSet& siblings) { + siblings.disable_all(); + char path[256]; sprintf(path, "/sys/devices/system/cpu/cpu%d/topology/thread_siblings", cpuid); FILE* fp = 0; //fopen(path, "rb"); if (fp) { - int thread_siblings = -1; - int nscan = fscanf(fp, "%x", &thread_siblings); - if (nscan != 1) + // Try to read hex mask directly (this path is currently disabled) + char hex_str[256]; + int nscan = fscanf(fp, "%255s", hex_str); + if (nscan == 1) { - // ignore + // Parse hex string into CpuSet + int len = strlen(hex_str); + if (hex_str[0] == '0' && hex_str[1] == 'x') + { + // Skip "0x" prefix + len -= 2; + memmove(hex_str, hex_str + 2, len + 1); + } + + int ci = 0; + for (int i = len - 1; i >= 0; i--) + { + char c = hex_str[i]; + int hex_val = 0; + + if (c >= '0' && c <= '9') + hex_val = c - '0'; + else if (c >= 'a' && c <= 'f') + hex_val = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + hex_val = c - 'A' + 10; + else + continue; + + if (hex_val & 1) siblings.enable(ci + 0); + if (hex_val & 2) siblings.enable(ci + 1); + if (hex_val & 4) siblings.enable(ci + 2); + if (hex_val & 8) siblings.enable(ci + 3); + + ci += 4; + } } fclose(fp); - return thread_siblings; + if (!siblings.is_empty()) + return; } // second try, parse from human-readable thread_siblings_list @@ -942,8 +979,6 @@ static int get_thread_siblings(int cpuid) fp = fopen(path, "rb"); if (fp) { - int thread_siblings = -1; - int id0; char sep; int id1; @@ -951,36 +986,28 @@ static int get_thread_siblings(int cpuid) int nscan = fscanf(fp, "%d", &id0); if (nscan == 1) { - thread_siblings = (1 << id0); + siblings.enable(id0); while (fscanf(fp, "%c%d", &sep, &id1) == 2) { if (sep == ',') { - thread_siblings |= (1 << id1); + siblings.enable(id1); } if (sep == '-' && id0 < id1) { for (int i = id0 + 1; i <= id1; i++) { - thread_siblings |= (1 << i); + siblings.enable(i); } } id0 = id1; } } - else - { - // ignore - } fclose(fp); - - return thread_siblings; } - - return -1; } #endif // defined __ANDROID__ || defined __linux__ @@ -1017,11 +1044,12 @@ static int get_physical_cpucount() free(buffer); #elif defined __ANDROID__ || defined __linux__ - std::vector thread_set; + std::vector thread_set; for (int i = 0; i < g_cpucount; i++) { - int thread_siblings = get_thread_siblings(i); - if (thread_siblings == -1) + ncnn::CpuSet thread_siblings; + get_thread_siblings(i, thread_siblings); + if (thread_siblings.is_empty()) { // ignore malformed one continue; @@ -1030,7 +1058,18 @@ static int get_physical_cpucount() bool thread_siblings_exists = false; for (size_t j = 0; j < thread_set.size(); j++) { - if (thread_set[j] == thread_siblings) + // Compare CpuSets by checking if they have the same enabled CPUs + bool same = true; + int max_cpu = std::max(thread_siblings.max_cpu_id(), thread_set[j].max_cpu_id()); + for (int k = 0; k <= max_cpu; k++) + { + if (thread_siblings.is_enabled(k) != thread_set[j].is_enabled(k)) + { + same = false; + break; + } + } + if (same) { thread_siblings_exists = true; break; @@ -1153,11 +1192,24 @@ static int get_data_cache_size(int cpuid, int level) int ci = 0; for (int i = len - 1; i >= 0; i--) { - char x = shared_cpu_map_str[i]; - if (x & 1) shared_cpu_map.enable(ci + 0); - if (x & 2) shared_cpu_map.enable(ci + 1); - if (x & 4) shared_cpu_map.enable(ci + 2); - if (x & 8) shared_cpu_map.enable(ci + 3); + char c = shared_cpu_map_str[i]; + int hex_val = 0; + + // Convert hex character to value + if (c >= '0' && c <= '9') + hex_val = c - '0'; + else if (c >= 'a' && c <= 'f') + hex_val = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + hex_val = c - 'A' + 10; + else + continue; // Skip invalid characters + + // Set bits according to hex value + if (hex_val & 1) shared_cpu_map.enable(ci + 0); + if (hex_val & 2) shared_cpu_map.enable(ci + 1); + if (hex_val & 4) shared_cpu_map.enable(ci + 2); + if (hex_val & 8) shared_cpu_map.enable(ci + 3); ci += 4; } @@ -1169,14 +1221,15 @@ static int get_data_cache_size(int cpuid, int level) // resolve physical cpu count in the shared_cpu_map int shared_physical_cpu_count = 0; { - std::vector thread_set; + std::vector thread_set; for (int i = 0; i < g_cpucount; i++) { if (!shared_cpu_map.is_enabled(i)) continue; - int thread_siblings = get_thread_siblings(i); - if (thread_siblings == -1) + ncnn::CpuSet thread_siblings; + get_thread_siblings(i, thread_siblings); + if (thread_siblings.is_empty()) { // ignore malformed one continue; @@ -1185,7 +1238,18 @@ static int get_data_cache_size(int cpuid, int level) bool thread_siblings_exists = false; for (size_t j = 0; j < thread_set.size(); j++) { - if (thread_set[j] == thread_siblings) + // Compare CpuSets by checking if they have the same enabled CPUs + bool same = true; + int max_cpu = std::max(thread_siblings.max_cpu_id(), thread_set[j].max_cpu_id()); + for (int k = 0; k <= max_cpu; k++) + { + if (thread_siblings.is_enabled(k) != thread_set[j].is_enabled(k)) + { + same = false; + break; + } + } + if (same) { thread_siblings_exists = true; break; @@ -1373,11 +1437,17 @@ static ncnn::CpuSet get_smt_cpu_mask() if (ptr->Relationship == RelationProcessorCore) { ncnn::CpuSet smt_set; - smt_set.mask = ptr->ProcessorMask; + smt_set.set_legacy_mask(ptr->ProcessorMask); if (smt_set.num_enabled() > 1) { - // this core is smt - smt_cpu_mask.mask |= smt_set.mask; + // this core is smt - merge with existing smt_cpu_mask + for (int i = 0; i < 64; i++) // ProcessorMask is limited to 64 bits + { + if (smt_set.is_enabled(i)) + { + smt_cpu_mask.enable(i); + } + } } } @@ -1432,14 +1502,73 @@ static std::vector get_max_freq_mhz() static int set_sched_affinity(const ncnn::CpuSet& thread_affinity_mask) { - DWORD_PTR prev_mask = SetThreadAffinityMask(GetCurrentThread(), thread_affinity_mask.mask); - if (prev_mask == 0) + // Check if we can use the legacy method (<=64 CPUs) + int max_cpu = thread_affinity_mask.max_cpu_id(); + if (max_cpu < 64) { - NCNN_LOGE("SetThreadAffinityMask failed %d", GetLastError()); + ULONG_PTR legacy_mask = thread_affinity_mask.get_legacy_mask(); + if (legacy_mask != 0) + { + DWORD_PTR prev_mask = SetThreadAffinityMask(GetCurrentThread(), legacy_mask); + if (prev_mask == 0) + { + NCNN_LOGE("SetThreadAffinityMask failed %d", GetLastError()); + return -1; + } + return 0; + } + } + + // For >64 CPU support, use SetThreadGroupAffinity + // Windows organizes CPUs into groups of 64 + typedef BOOL(WINAPI * LPFN_STGA)(HANDLE, const GROUP_AFFINITY*, GROUP_AFFINITY*); + + HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + { + NCNN_LOGE("Failed to get kernel32.dll handle"); return -1; } - return 0; + LPFN_STGA SetThreadGroupAffinityFunc = (LPFN_STGA)GetProcAddress(kernel32, "SetThreadGroupAffinity"); + if (!SetThreadGroupAffinityFunc) + { + NCNN_LOGE("SetThreadGroupAffinity not available, >64 CPU affinity not supported"); + return -1; + } + + // Find the first enabled CPU and set affinity to its group + // This is a simplified implementation - ideally we'd handle multiple groups + for (int cpu = 0; cpu <= max_cpu; cpu++) + { + if (thread_affinity_mask.is_enabled(cpu)) + { + GROUP_AFFINITY group_affinity = {0}; + group_affinity.Group = (WORD)(cpu / 64); + group_affinity.Mask = 1ULL << (cpu % 64); + + // Add other CPUs in the same group + for (int other_cpu = cpu + 1; other_cpu <= max_cpu && other_cpu < (group_affinity.Group + 1) * 64; other_cpu++) + { + if (thread_affinity_mask.is_enabled(other_cpu)) + { + group_affinity.Mask |= 1ULL << (other_cpu % 64); + } + } + + GROUP_AFFINITY prev_affinity; + if (!SetThreadGroupAffinityFunc(GetCurrentThread(), &group_affinity, &prev_affinity)) + { + NCNN_LOGE("SetThreadGroupAffinity failed %d", GetLastError()); + return -1; + } + + return 0; + } + } + + NCNN_LOGE("No CPUs enabled in affinity mask"); + return -1; } #endif // defined _WIN32 @@ -1560,7 +1689,14 @@ static int set_sched_affinity(const ncnn::CpuSet& thread_affinity_mask) pid_t pid = syscall(SYS_gettid); #endif - int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(cpu_set_t), &thread_affinity_mask.cpu_set); + const cpu_set_t* cpuset = thread_affinity_mask.get_cpu_set(); + if (!cpuset) + { + NCNN_LOGE("Failed to get cpu_set from CpuSet"); + return -1; + } + + int syscallret = syscall(__NR_sched_setaffinity, pid, CPU_ALLOC_SIZE(CPU_SETSIZE), cpuset); if (syscallret) { NCNN_LOGE("syscall error %d", syscallret); @@ -1583,7 +1719,8 @@ static int set_sched_affinity(const ncnn::CpuSet& thread_affinity_mask) // see https://github.com/Tencent/ncnn/pull/2335#discussion_r528233919 --- AmeAkio int affinity_tag = THREAD_AFFINITY_TAG_NULL; - for (int i = 0; i < (int)sizeof(thread_affinity_mask.policy) * 8; i++) + int max_cpu = thread_affinity_mask.max_cpu_id(); + for (int i = 0; i <= max_cpu && i < 32; i++) // Apple policy is limited to 32 bits { if (thread_affinity_mask.is_enabled(i)) { @@ -2052,13 +2189,25 @@ static int get_sched_affinity(ncnn::CpuSet& thread_affinity_mask) thread_affinity_mask.disable_all(); - int syscallret = syscall(__NR_sched_getaffinity, pid, sizeof(cpu_set_t), &thread_affinity_mask.cpu_set); + // Allocate a temporary cpu_set_t for the syscall + cpu_set_t* temp_cpuset = CPU_ALLOC(CPU_SETSIZE); + if (!temp_cpuset) + { + return -1; + } + + int syscallret = syscall(__NR_sched_getaffinity, pid, CPU_ALLOC_SIZE(CPU_SETSIZE), temp_cpuset); if (syscallret) { + CPU_FREE(temp_cpuset); // handle get error silently return -1; } + // Copy the result to our CpuSet + thread_affinity_mask.set_cpu_set(temp_cpuset); + CPU_FREE(temp_cpuset); + return 0; } @@ -2149,6 +2298,10 @@ static void initialize_global_cpu_info() g_cpucount = get_cpucount(); g_physical_cpucount = get_physical_cpucount(); g_powersave = 0; + + // Set global max CPU count for CpuSet optimization + g_max_cpu_count = g_cpucount; + initialize_cpu_thread_affinity_mask(g_cpu_affinity_mask_all, g_cpu_affinity_mask_little, g_cpu_affinity_mask_big); #if (defined _WIN32 && (__aarch64__ || __arm__)) || ((defined __ANDROID__ || defined __linux__) && __riscv) @@ -2265,142 +2418,506 @@ static inline void try_initialize_global_cpu_info() namespace ncnn { -#if defined _WIN32 +// New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) +#if defined _WIN32 + , legacy_mask_cache(0) + , legacy_mask_valid(false) +#endif +#if defined __ANDROID__ || defined __linux__ + , cpu_set_cache(nullptr) + , cpu_set_valid(false) +#endif +#if __APPLE__ + , legacy_policy_cache(0) + , legacy_policy_valid(false) +#endif { - disable_all(); } -void CpuSet::enable(int cpu) +CpuSet::CpuSet(const CpuSet& other) + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) +#if defined _WIN32 + , legacy_mask_cache(0) + , legacy_mask_valid(false) +#endif +#if defined __ANDROID__ || defined __linux__ + , cpu_set_cache(nullptr) + , cpu_set_valid(false) +#endif +#if __APPLE__ + , legacy_policy_cache(0) + , legacy_policy_valid(false) +#endif { - mask |= ((ULONG_PTR)1 << cpu); + copy_from(other); } -void CpuSet::disable(int cpu) +CpuSet& CpuSet::operator=(const CpuSet& other) { - mask &= ~((ULONG_PTR)1 << cpu); + if (this != &other) + { + copy_from(other); + } + return *this; } -void CpuSet::disable_all() +CpuSet::~CpuSet() { - mask = 0; + if (extended_mask) + { + free(extended_mask); + } +#if defined __ANDROID__ || defined __linux__ + if (cpu_set_cache) + { + CPU_FREE(cpu_set_cache); + } +#endif } -bool CpuSet::is_enabled(int cpu) const +void CpuSet::copy_from(const CpuSet& other) { - return mask & ((ULONG_PTR)1 << cpu); -} + // Clean up existing state + if (extended_mask) + { + free(extended_mask); + extended_mask = nullptr; + } + extended_capacity = 0; -int CpuSet::num_enabled() const -{ - int num_enabled = 0; - for (int i = 0; i < (int)sizeof(mask) * 8; i++) + // Copy basic state + fast_mask = other.fast_mask; + use_extended = other.use_extended; + + // Copy extended mask if needed + if (other.use_extended && other.extended_mask) { - if (is_enabled(i)) - num_enabled++; + extended_capacity = other.extended_capacity; + extended_mask = (uint64_t*)malloc(extended_capacity * sizeof(uint64_t)); + if (extended_mask) + { + memcpy(extended_mask, other.extended_mask, extended_capacity * sizeof(uint64_t)); + } } - return num_enabled; + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; + if (cpu_set_cache) + { + CPU_FREE(cpu_set_cache); + cpu_set_cache = nullptr; + } +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } -#elif defined __ANDROID__ || defined __linux__ -CpuSet::CpuSet() + +void CpuSet::ensure_capacity(int cpu_id) { - disable_all(); -} + if (cpu_id < FAST_PATH_BITS && !use_extended) + { + return; // Fast path is sufficient + } + // Need to switch to extended mode + if (!use_extended) + { + use_extended = true; + // Calculate required capacity + int required_words = (cpu_id / BITS_PER_WORD) + 1; + extended_capacity = std::max(required_words, 2); // Minimum 2 words + extended_mask = (uint64_t*)calloc(extended_capacity, sizeof(uint64_t)); + if (extended_mask) + { + // Copy fast_mask to extended_mask[0] + extended_mask[0] = fast_mask; + } + return; + } + + // Already in extended mode, check if we need more capacity + int required_words = (cpu_id / BITS_PER_WORD) + 1; + if (required_words > extended_capacity) + { + int new_capacity = std::max(required_words, extended_capacity * 2); + uint64_t* new_mask = (uint64_t*)realloc(extended_mask, new_capacity * sizeof(uint64_t)); + if (new_mask) + { + // Zero out new memory + memset(new_mask + extended_capacity, 0, (new_capacity - extended_capacity) * sizeof(uint64_t)); + extended_mask = new_mask; + extended_capacity = new_capacity; + } + } +} void CpuSet::enable(int cpu) { - CPU_SET(cpu, &cpu_set); + if (cpu < 0) return; + + ensure_capacity(cpu); + + if (!use_extended && cpu < FAST_PATH_BITS) + { + fast_mask |= (1ULL << cpu); + } + else if (use_extended && extended_mask) + { + int word_idx = cpu / BITS_PER_WORD; + int bit_idx = cpu % BITS_PER_WORD; + if (word_idx < extended_capacity) + { + extended_mask[word_idx] |= (1ULL << bit_idx); + } + } + + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } void CpuSet::disable(int cpu) { - CPU_CLR(cpu, &cpu_set); + if (cpu < 0) return; + + if (!use_extended && cpu < FAST_PATH_BITS) + { + fast_mask &= ~(1ULL << cpu); + } + else if (use_extended && extended_mask) + { + int word_idx = cpu / BITS_PER_WORD; + int bit_idx = cpu % BITS_PER_WORD; + if (word_idx < extended_capacity) + { + extended_mask[word_idx] &= ~(1ULL << bit_idx); + } + } + + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } void CpuSet::disable_all() { - CPU_ZERO(&cpu_set); + fast_mask = 0; + if (use_extended && extended_mask) + { + memset(extended_mask, 0, extended_capacity * sizeof(uint64_t)); + } + + // Invalidate caches +#if defined _WIN32 + legacy_mask_valid = false; +#endif +#if defined __ANDROID__ || defined __linux__ + cpu_set_valid = false; +#endif +#if __APPLE__ + legacy_policy_valid = false; +#endif } bool CpuSet::is_enabled(int cpu) const { - return CPU_ISSET(cpu, &cpu_set); -} + if (cpu < 0) return false; -int CpuSet::num_enabled() const -{ - int num_enabled = 0; - for (int i = 0; i < (int)sizeof(cpu_set_t) * 8; i++) + if (!use_extended && cpu < FAST_PATH_BITS) + { + return (fast_mask & (1ULL << cpu)) != 0; + } + else if (use_extended && extended_mask) { - if (is_enabled(i)) - num_enabled++; + int word_idx = cpu / BITS_PER_WORD; + int bit_idx = cpu % BITS_PER_WORD; + if (word_idx < extended_capacity) + { + return (extended_mask[word_idx] & (1ULL << bit_idx)) != 0; + } } - return num_enabled; + return false; } -#elif __APPLE__ -CpuSet::CpuSet() +// Helper function to count bits in a 64-bit integer +static int popcount64(uint64_t x) { - disable_all(); +#if defined(__GNUC__) || defined(__clang__) + return __builtin_popcountll(x); +#elif defined(_MSC_VER) + return (int)__popcnt64(x); +#else + // Fallback implementation + int count = 0; + while (x) + { + count += x & 1; + x >>= 1; + } + return count; +#endif } -void CpuSet::enable(int cpu) +int CpuSet::num_enabled() const { - policy |= ((unsigned int)1 << cpu); + int count = 0; + + if (!use_extended) + { + // Fast path: count bits in fast_mask + count = popcount64(fast_mask); + } + else if (extended_mask) + { + // Extended path: count bits in all words + for (int i = 0; i < extended_capacity; i++) + { + count += popcount64(extended_mask[i]); + } + } + + return count; } -void CpuSet::disable(int cpu) +int CpuSet::max_cpu_id() const { - policy &= ~((unsigned int)1 << cpu); + if (!use_extended) + { + if (fast_mask == 0) return -1; + + // Find highest set bit in fast_mask + for (int i = FAST_PATH_BITS - 1; i >= 0; i--) + { + if (fast_mask & (1ULL << i)) + return i; + } + return -1; + } + else if (extended_mask) + { + // Find highest set bit in extended_mask + for (int word = extended_capacity - 1; word >= 0; word--) + { + if (extended_mask[word] != 0) + { + for (int bit = BITS_PER_WORD - 1; bit >= 0; bit--) + { + if (extended_mask[word] & (1ULL << bit)) + return word * BITS_PER_WORD + bit; + } + } + } + } + + return -1; } -void CpuSet::disable_all() +bool CpuSet::is_empty() const { - policy = 0; + if (!use_extended) + { + return fast_mask == 0; + } + else if (extended_mask) + { + for (int i = 0; i < extended_capacity; i++) + { + if (extended_mask[i] != 0) + return false; + } + } + + return true; } -bool CpuSet::is_enabled(int cpu) const +void CpuSet::set_range(int start_cpu, int end_cpu, bool enabled) { - return policy & ((unsigned int)1 << cpu); -} + if (start_cpu < 0 || end_cpu < start_cpu) return; -int CpuSet::num_enabled() const + for (int cpu = start_cpu; cpu <= end_cpu; cpu++) + { + if (enabled) + enable(cpu); + else + disable(cpu); + } +} +// Platform-specific compatibility methods +#if defined _WIN32 +ULONG_PTR CpuSet::get_legacy_mask() const { - int num_enabled = 0; - for (int i = 0; i < (int)sizeof(policy) * 8; i++) + if (!legacy_mask_valid) { - if (is_enabled(i)) - num_enabled++; + legacy_mask_cache = 0; + + if (!use_extended) + { + // Fast path: directly use fast_mask (truncated to ULONG_PTR size) + legacy_mask_cache = (ULONG_PTR)(fast_mask & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + } + else if (extended_mask && extended_capacity > 0) + { + // Extended path: use first word, truncated to ULONG_PTR size + legacy_mask_cache = (ULONG_PTR)(extended_mask[0] & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + } + + legacy_mask_valid = true; } - return num_enabled; + return legacy_mask_cache; } -#else -CpuSet::CpuSet() + +void CpuSet::set_legacy_mask(ULONG_PTR mask) { + disable_all(); + + // Set bits according to the legacy mask + for (int i = 0; i < (int)(sizeof(ULONG_PTR) * 8); i++) + { + if (mask & ((ULONG_PTR)1 << i)) + { + enable(i); + } + } } +#endif -void CpuSet::enable(int /* cpu */) +#if defined __ANDROID__ || defined __linux__ +const cpu_set_t* CpuSet::get_cpu_set() const { + if (!cpu_set_valid) + { + // Allocate cpu_set_t if not already done + if (!cpu_set_cache) + { + cpu_set_cache = CPU_ALLOC(CPU_SETSIZE); + if (!cpu_set_cache) + return nullptr; + } + + CPU_ZERO_S(CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); + + // Copy our internal representation to cpu_set_t + if (!use_extended) + { + for (int i = 0; i < FAST_PATH_BITS && i < CPU_SETSIZE; i++) + { + if (fast_mask & (1ULL << i)) + { + CPU_SET_S(i, CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); + } + } + } + else if (extended_mask) + { + for (int word = 0; word < extended_capacity; word++) + { + uint64_t mask = extended_mask[word]; + for (int bit = 0; bit < BITS_PER_WORD; bit++) + { + int cpu_id = word * BITS_PER_WORD + bit; + if (cpu_id >= CPU_SETSIZE) break; + + if (mask & (1ULL << bit)) + { + CPU_SET_S(cpu_id, CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); + } + } + if ((word + 1) * BITS_PER_WORD >= CPU_SETSIZE) break; + } + } + + cpu_set_valid = true; + } + + return cpu_set_cache; } -void CpuSet::disable(int /* cpu */) +cpu_set_t* CpuSet::get_cpu_set_mutable() { + get_cpu_set(); // Ensure cache is valid + return cpu_set_cache; } -void CpuSet::disable_all() +void CpuSet::set_cpu_set(const cpu_set_t* cpuset) { + if (!cpuset) return; + + disable_all(); + + // Copy from cpu_set_t to our internal representation + for (int i = 0; i < CPU_SETSIZE; i++) + { + if (CPU_ISSET(i, cpuset)) + { + enable(i); + } + } } +#endif -bool CpuSet::is_enabled(int /* cpu */) const +#if __APPLE__ +unsigned int CpuSet::get_legacy_policy() const { - return true; + if (!legacy_policy_valid) + { + legacy_policy_cache = 0; + + if (!use_extended) + { + // Fast path: directly use fast_mask (truncated to 32 bits) + legacy_policy_cache = (unsigned int)(fast_mask & 0xFFFFFFFFU); + } + else if (extended_mask && extended_capacity > 0) + { + // Extended path: use first word, truncated to 32 bits + legacy_policy_cache = (unsigned int)(extended_mask[0] & 0xFFFFFFFFU); + } + + legacy_policy_valid = true; + } + + return legacy_policy_cache; } -int CpuSet::num_enabled() const +void CpuSet::set_legacy_policy(unsigned int policy) { - return get_cpu_count(); + disable_all(); + + // Set bits according to the legacy policy + for (int i = 0; i < 32; i++) + { + if (policy & (1U << i)) + { + enable(i); + } + } } #endif @@ -3065,7 +3582,8 @@ int set_cpu_thread_affinity(const CpuSet& thread_affinity_mask) { // assign one core for each thread int core = -1 - i; - for (int j = 0; j < (int)sizeof(thread_affinity_mask.policy) * 8; j++) + int max_cpu = thread_affinity_mask.max_cpu_id(); + for (int j = 0; j <= max_cpu && j < 32; j++) // Apple policy is limited to 32 bits { if (thread_affinity_mask.is_enabled(j)) { diff --git a/src/cpu.h b/src/cpu.h index cbf417111..1a13636bc 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -22,21 +22,64 @@ class NCNN_EXPORT CpuSet { public: CpuSet(); + CpuSet(const CpuSet& other); + CpuSet& operator=(const CpuSet& other); + ~CpuSet(); + void enable(int cpu); void disable(int cpu); void disable_all(); bool is_enabled(int cpu) const; int num_enabled() const; -public: + // New methods for >64 CPU support + int max_cpu_id() const; + bool is_empty() const; + void set_range(int start_cpu, int end_cpu, bool enabled); + + // Platform-specific accessors for backward compatibility +#if defined _WIN32 + ULONG_PTR get_legacy_mask() const; + void set_legacy_mask(ULONG_PTR mask); +#endif +#if defined __ANDROID__ || defined __linux__ + const cpu_set_t* get_cpu_set() const; + cpu_set_t* get_cpu_set_mutable(); + void set_cpu_set(const cpu_set_t* cpuset); +#endif +#if __APPLE__ + unsigned int get_legacy_policy() const; + void set_legacy_policy(unsigned int policy); +#endif + +private: + void ensure_capacity(int cpu_id); + void copy_from(const CpuSet& other); + + // Internal implementation details + static const int FAST_PATH_BITS = 64; + static const int BITS_PER_WORD = 64; + + // Fast path for systems with <= 64 CPUs + uint64_t fast_mask; + + // Extended path for systems with > 64 CPUs + uint64_t* extended_mask; + int extended_capacity; // in number of uint64_t words + bool use_extended; + + // Platform-specific storage for compatibility #if defined _WIN32 - ULONG_PTR mask; + mutable ULONG_PTR legacy_mask_cache; + mutable bool legacy_mask_valid; #endif #if defined __ANDROID__ || defined __linux__ - cpu_set_t cpu_set; + mutable cpu_set_t* cpu_set_cache; + mutable bool cpu_set_valid; #endif #if __APPLE__ - unsigned int policy; + mutable unsigned int legacy_policy_cache; + mutable bool legacy_policy_valid; #endif }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9d5b6517e..e94c52b87 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,9 +60,16 @@ endif() ncnn_add_test(c_api) ncnn_add_test(cpu) +ncnn_add_test(cpu_large) +ncnn_add_test(cpu_simulation) ncnn_add_test(expression) ncnn_add_test(paramdict) +# Add validate_cpu_support test manually +add_executable(test_validate_cpu_support validate_cpu_support.cpp) +target_link_libraries(test_validate_cpu_support ncnn) +add_test(NAME test_validate_cpu_support COMMAND test_validate_cpu_support) + if(NCNN_VULKAN) ncnn_add_test(command) endif() diff --git a/tests/test_cpu_large.cpp b/tests/test_cpu_large.cpp new file mode 100644 index 000000000..749177864 --- /dev/null +++ b/tests/test_cpu_large.cpp @@ -0,0 +1,250 @@ +// Copyright 2024 Tencent +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include + +#include "cpu.h" + +// Test CpuSet with >64 CPUs +static int test_cpuset_large() +{ + printf("Testing CpuSet with >64 CPUs...\n"); + + ncnn::CpuSet set; + + // Test basic operations with large CPU IDs + const int test_cpus[] = {0, 63, 64, 65, 127, 128, 255, 256, 511, 512, 1023}; + const int num_test_cpus = sizeof(test_cpus) / sizeof(test_cpus[0]); + + // Initially all should be disabled + for (int i = 0; i < num_test_cpus; i++) + { + if (set.is_enabled(test_cpus[i])) + { + fprintf(stderr, "CPU %d should be disabled initially\n", test_cpus[i]); + return 1; + } + } + + if (set.num_enabled() != 0) + { + fprintf(stderr, "Initially no CPUs should be enabled\n"); + return 1; + } + + if (!set.is_empty()) + { + fprintf(stderr, "Initially CpuSet should be empty\n"); + return 1; + } + + // Enable all test CPUs + for (int i = 0; i < num_test_cpus; i++) + { + set.enable(test_cpus[i]); + } + + // Verify they are enabled + for (int i = 0; i < num_test_cpus; i++) + { + if (!set.is_enabled(test_cpus[i])) + { + fprintf(stderr, "CPU %d should be enabled\n", test_cpus[i]); + return 1; + } + } + + if (set.num_enabled() != num_test_cpus) + { + fprintf(stderr, "Expected %d enabled CPUs, got %d\n", num_test_cpus, set.num_enabled()); + return 1; + } + + if (set.is_empty()) + { + fprintf(stderr, "CpuSet should not be empty after enabling CPUs\n"); + return 1; + } + + // Test max_cpu_id + int max_cpu = set.max_cpu_id(); + if (max_cpu != 1023) + { + fprintf(stderr, "Expected max CPU ID 1023, got %d\n", max_cpu); + return 1; + } + + // Test disable + set.disable(test_cpus[0]); + if (set.is_enabled(test_cpus[0])) + { + fprintf(stderr, "CPU %d should be disabled after disable()\n", test_cpus[0]); + return 1; + } + + if (set.num_enabled() != num_test_cpus - 1) + { + fprintf(stderr, "Expected %d enabled CPUs after disable, got %d\n", + num_test_cpus - 1, set.num_enabled()); + return 1; + } + + // Test set_range + set.disable_all(); + set.set_range(100, 200, true); + + int expected_range_count = 200 - 100 + 1; + if (set.num_enabled() != expected_range_count) + { + fprintf(stderr, "Expected %d CPUs in range [100,200], got %d\n", + expected_range_count, set.num_enabled()); + return 1; + } + + for (int i = 100; i <= 200; i++) + { + if (!set.is_enabled(i)) + { + fprintf(stderr, "CPU %d should be enabled in range [100,200]\n", i); + return 1; + } + } + + // Test copy constructor + ncnn::CpuSet set_copy(set); + if (set_copy.num_enabled() != set.num_enabled()) + { + fprintf(stderr, "Copy constructor failed: different num_enabled\n"); + return 1; + } + + for (int i = 0; i <= 1023; i++) + { + if (set_copy.is_enabled(i) != set.is_enabled(i)) + { + fprintf(stderr, "Copy constructor failed: CPU %d state differs\n", i); + return 1; + } + } + + // Test assignment operator + ncnn::CpuSet set_assigned; + set_assigned.enable(999); + set_assigned = set; + + if (set_assigned.num_enabled() != set.num_enabled()) + { + fprintf(stderr, "Assignment operator failed: different num_enabled\n"); + return 1; + } + + for (int i = 0; i <= 1023; i++) + { + if (set_assigned.is_enabled(i) != set.is_enabled(i)) + { + fprintf(stderr, "Assignment operator failed: CPU %d state differs\n", i); + return 1; + } + } + + printf("CpuSet large CPU test passed!\n"); + return 0; +} + +// Test boundary conditions +static int test_cpuset_boundary() +{ + printf("Testing CpuSet boundary conditions...\n"); + + ncnn::CpuSet set; + + // Test CPU ID 0 + set.enable(0); + if (!set.is_enabled(0)) + { + fprintf(stderr, "CPU 0 should be enabled\n"); + return 1; + } + + // Test exactly 64 CPUs (boundary between fast and extended path) + set.disable_all(); + for (int i = 0; i < 64; i++) + { + set.enable(i); + } + + if (set.num_enabled() != 64) + { + fprintf(stderr, "Expected 64 enabled CPUs, got %d\n", set.num_enabled()); + return 1; + } + + // Test 65th CPU (should trigger extended mode) + set.enable(64); + if (set.num_enabled() != 65) + { + fprintf(stderr, "Expected 65 enabled CPUs, got %d\n", set.num_enabled()); + return 1; + } + + // Test negative CPU ID (should be ignored) + set.enable(-1); + set.disable(-1); + // Should not crash + + // Test very large CPU ID + set.enable(10000); + if (!set.is_enabled(10000)) + { + fprintf(stderr, "CPU 10000 should be enabled\n"); + return 1; + } + + printf("CpuSet boundary test passed!\n"); + return 0; +} + +// Test performance with large CPU sets +static int test_cpuset_performance() +{ + printf("Testing CpuSet performance with large CPU sets...\n"); + + ncnn::CpuSet set; + + // Enable many CPUs + const int max_cpu = 2048; + for (int i = 0; i < max_cpu; i += 2) // Enable every other CPU + { + set.enable(i); + } + + // Verify count + int expected_count = max_cpu / 2; + if (set.num_enabled() != expected_count) + { + fprintf(stderr, "Expected %d enabled CPUs, got %d\n", expected_count, set.num_enabled()); + return 1; + } + + // Test copy performance + ncnn::CpuSet set_copy(set); + if (set_copy.num_enabled() != expected_count) + { + fprintf(stderr, "Copy failed: expected %d enabled CPUs, got %d\n", + expected_count, set_copy.num_enabled()); + return 1; + } + + printf("CpuSet performance test passed!\n"); + return 0; +} + +int main() +{ + return 0 + || test_cpuset_large() + || test_cpuset_boundary() + || test_cpuset_performance(); +} From f83865a4609802127775d3a788a8ecc7193510c8 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Sun, 13 Jul 2025 21:58:26 +0800 Subject: [PATCH 14/26] Support for >64 CPU systems --- 64 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 64 diff --git a/64 b/64 new file mode 100644 index 000000000..e69de29bb From b493070c8776935250570fa64a154cf459baac1f Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Tue, 15 Jul 2025 11:21:14 +0800 Subject: [PATCH 15/26] Support for >64 CPU systems in NCNN.Fix CMakeLists error. --- tests/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e94c52b87..b5f4634ca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -61,15 +61,9 @@ endif() ncnn_add_test(c_api) ncnn_add_test(cpu) ncnn_add_test(cpu_large) -ncnn_add_test(cpu_simulation) ncnn_add_test(expression) ncnn_add_test(paramdict) -# Add validate_cpu_support test manually -add_executable(test_validate_cpu_support validate_cpu_support.cpp) -target_link_libraries(test_validate_cpu_support ncnn) -add_test(NAME test_validate_cpu_support COMMAND test_validate_cpu_support) - if(NCNN_VULKAN) ncnn_add_test(command) endif() From e38e779f40b2ad540ae9879da4fa70dfe9fa62fb Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Wed, 16 Jul 2025 21:38:27 +0800 Subject: [PATCH 16/26] Fix uint64_t compilation errors and implement >64 CPU support - Add #include to cpu.h, cpu.cpp, and platform.h.in - Implement extended CpuSet class supporting >64 CPUs - Add fast path for <=64 CPUs and extended path for >64 CPUs - Include necessary headers for std::max, std::vector, memset, etc. - Fix original code's missing stdint.h includes for uint64_t usage - Maintain backward compatibility with platform-specific APIs Fixes #6142 --- src/cpu.cpp | 1 + src/cpu.h | 1 + src/platform.h.in | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/cpu.cpp b/src/cpu.cpp index 8ed93c409..e0b347bfa 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -5,6 +5,7 @@ #include "platform.h" +#include #include #ifndef __wasi__ #include diff --git a/src/cpu.h b/src/cpu.h index 1a13636bc..8d77b2736 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -5,6 +5,7 @@ #define NCNN_CPU_H #include +#include #if defined _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/src/platform.h.in b/src/platform.h.in index 7bb27c6b0..6ee6a497c 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -4,6 +4,8 @@ #ifndef NCNN_PLATFORM_H #define NCNN_PLATFORM_H +#include + #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING #cmakedefine01 NCNN_SIMPLEOCV From b18ec231fbba9053a889ce3d44e6c76922c363db Mon Sep 17 00:00:00 2001 From: yok7 <145077166+yok7@users.noreply.github.com> Date: Wed, 16 Jul 2025 14:10:47 +0000 Subject: [PATCH 17/26] apply code-format changes --- src/cpu.cpp | 40 ++++++++++----------- tests/test_cpu_large.cpp | 76 ++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index e0b347bfa..b18d0d3f5 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -2421,41 +2421,41 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { copy_from(other); diff --git a/tests/test_cpu_large.cpp b/tests/test_cpu_large.cpp index 749177864..3dcebd7b5 100644 --- a/tests/test_cpu_large.cpp +++ b/tests/test_cpu_large.cpp @@ -11,13 +11,13 @@ static int test_cpuset_large() { printf("Testing CpuSet with >64 CPUs...\n"); - + ncnn::CpuSet set; - + // Test basic operations with large CPU IDs const int test_cpus[] = {0, 63, 64, 65, 127, 128, 255, 256, 511, 512, 1023}; const int num_test_cpus = sizeof(test_cpus) / sizeof(test_cpus[0]); - + // Initially all should be disabled for (int i = 0; i < num_test_cpus; i++) { @@ -27,25 +27,25 @@ static int test_cpuset_large() return 1; } } - + if (set.num_enabled() != 0) { fprintf(stderr, "Initially no CPUs should be enabled\n"); return 1; } - + if (!set.is_empty()) { fprintf(stderr, "Initially CpuSet should be empty\n"); return 1; } - + // Enable all test CPUs for (int i = 0; i < num_test_cpus; i++) { set.enable(test_cpus[i]); } - + // Verify they are enabled for (int i = 0; i < num_test_cpus; i++) { @@ -55,19 +55,19 @@ static int test_cpuset_large() return 1; } } - + if (set.num_enabled() != num_test_cpus) { fprintf(stderr, "Expected %d enabled CPUs, got %d\n", num_test_cpus, set.num_enabled()); return 1; } - + if (set.is_empty()) { fprintf(stderr, "CpuSet should not be empty after enabling CPUs\n"); return 1; } - + // Test max_cpu_id int max_cpu = set.max_cpu_id(); if (max_cpu != 1023) @@ -75,7 +75,7 @@ static int test_cpuset_large() fprintf(stderr, "Expected max CPU ID 1023, got %d\n", max_cpu); return 1; } - + // Test disable set.disable(test_cpus[0]); if (set.is_enabled(test_cpus[0])) @@ -83,26 +83,26 @@ static int test_cpuset_large() fprintf(stderr, "CPU %d should be disabled after disable()\n", test_cpus[0]); return 1; } - + if (set.num_enabled() != num_test_cpus - 1) { - fprintf(stderr, "Expected %d enabled CPUs after disable, got %d\n", + fprintf(stderr, "Expected %d enabled CPUs after disable, got %d\n", num_test_cpus - 1, set.num_enabled()); return 1; } - + // Test set_range set.disable_all(); set.set_range(100, 200, true); - + int expected_range_count = 200 - 100 + 1; if (set.num_enabled() != expected_range_count) { - fprintf(stderr, "Expected %d CPUs in range [100,200], got %d\n", + fprintf(stderr, "Expected %d CPUs in range [100,200], got %d\n", expected_range_count, set.num_enabled()); return 1; } - + for (int i = 100; i <= 200; i++) { if (!set.is_enabled(i)) @@ -111,7 +111,7 @@ static int test_cpuset_large() return 1; } } - + // Test copy constructor ncnn::CpuSet set_copy(set); if (set_copy.num_enabled() != set.num_enabled()) @@ -119,7 +119,7 @@ static int test_cpuset_large() fprintf(stderr, "Copy constructor failed: different num_enabled\n"); return 1; } - + for (int i = 0; i <= 1023; i++) { if (set_copy.is_enabled(i) != set.is_enabled(i)) @@ -128,18 +128,18 @@ static int test_cpuset_large() return 1; } } - + // Test assignment operator ncnn::CpuSet set_assigned; set_assigned.enable(999); set_assigned = set; - + if (set_assigned.num_enabled() != set.num_enabled()) { fprintf(stderr, "Assignment operator failed: different num_enabled\n"); return 1; } - + for (int i = 0; i <= 1023; i++) { if (set_assigned.is_enabled(i) != set.is_enabled(i)) @@ -148,7 +148,7 @@ static int test_cpuset_large() return 1; } } - + printf("CpuSet large CPU test passed!\n"); return 0; } @@ -157,9 +157,9 @@ static int test_cpuset_large() static int test_cpuset_boundary() { printf("Testing CpuSet boundary conditions...\n"); - + ncnn::CpuSet set; - + // Test CPU ID 0 set.enable(0); if (!set.is_enabled(0)) @@ -167,20 +167,20 @@ static int test_cpuset_boundary() fprintf(stderr, "CPU 0 should be enabled\n"); return 1; } - + // Test exactly 64 CPUs (boundary between fast and extended path) set.disable_all(); for (int i = 0; i < 64; i++) { set.enable(i); } - + if (set.num_enabled() != 64) { fprintf(stderr, "Expected 64 enabled CPUs, got %d\n", set.num_enabled()); return 1; } - + // Test 65th CPU (should trigger extended mode) set.enable(64); if (set.num_enabled() != 65) @@ -188,12 +188,12 @@ static int test_cpuset_boundary() fprintf(stderr, "Expected 65 enabled CPUs, got %d\n", set.num_enabled()); return 1; } - + // Test negative CPU ID (should be ignored) set.enable(-1); set.disable(-1); // Should not crash - + // Test very large CPU ID set.enable(10000); if (!set.is_enabled(10000)) @@ -201,7 +201,7 @@ static int test_cpuset_boundary() fprintf(stderr, "CPU 10000 should be enabled\n"); return 1; } - + printf("CpuSet boundary test passed!\n"); return 0; } @@ -210,16 +210,16 @@ static int test_cpuset_boundary() static int test_cpuset_performance() { printf("Testing CpuSet performance with large CPU sets...\n"); - + ncnn::CpuSet set; - + // Enable many CPUs const int max_cpu = 2048; - for (int i = 0; i < max_cpu; i += 2) // Enable every other CPU + for (int i = 0; i < max_cpu; i += 2) // Enable every other CPU { set.enable(i); } - + // Verify count int expected_count = max_cpu / 2; if (set.num_enabled() != expected_count) @@ -227,16 +227,16 @@ static int test_cpuset_performance() fprintf(stderr, "Expected %d enabled CPUs, got %d\n", expected_count, set.num_enabled()); return 1; } - + // Test copy performance ncnn::CpuSet set_copy(set); if (set_copy.num_enabled() != expected_count) { - fprintf(stderr, "Copy failed: expected %d enabled CPUs, got %d\n", + fprintf(stderr, "Copy failed: expected %d enabled CPUs, got %d\n", expected_count, set_copy.num_enabled()); return 1; } - + printf("CpuSet performance test passed!\n"); return 0; } From 58595848bd7f667f292b390c8f50e2f059b8f47d Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 10:26:34 +0800 Subject: [PATCH 18/26] Add missing header for std::pair usage - Fix compilation error for std::pair usage in Windows processor detection - std::pair requires header to be explicitly included - Ensures compatibility across different compilers and environments --- src/cpu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cpu.cpp b/src/cpu.cpp index b18d0d3f5..5cb5a7f67 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #ifdef _OPENMP From 252f30680e4c823e5ba976a0ff730eec8f930bd2 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 14:52:23 +0800 Subject: [PATCH 19/26] Fix NCNN_SIMPLESTL compatibility and improve bit shift safety - Add conditional header includes for uint64_t in all build modes - Include in SIMPLESTL mode, in normal mode - Move standard library headers to conditional compilation blocks - Fix unsafe bit shift operations that could cause undefined behavior - Ensure >64 CPU support works correctly in both SIMPLESTL and normal modes - Tested successfully in NCNN_SIMPLESTL=ON mode --- src/cpu.cpp | 67 +++++++++++++++++++++++++++++++---------------- src/cpu.h | 1 - src/platform.h.in | 5 ++++ 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 5cb5a7f67..01f42685e 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -5,7 +5,6 @@ #include "platform.h" -#include #include #ifndef __wasi__ #include @@ -14,9 +13,13 @@ #include #include #include + +#if !NCNN_SIMPLESTL #include +#include #include #include +#endif #ifdef _OPENMP #if NCNN_SIMPLEOMP @@ -2422,41 +2425,41 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) #if defined _WIN32 - , - legacy_mask_cache(0), - legacy_mask_valid(false) + , legacy_mask_cache(0) + , legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , - cpu_set_cache(nullptr), - cpu_set_valid(false) + , cpu_set_cache(nullptr) + , cpu_set_valid(false) #endif #if __APPLE__ - , - legacy_policy_cache(0), - legacy_policy_valid(false) + , legacy_policy_cache(0) + , legacy_policy_valid(false) #endif { } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0) + , extended_mask(nullptr) + , extended_capacity(0) + , use_extended(false) #if defined _WIN32 - , - legacy_mask_cache(0), - legacy_mask_valid(false) + , legacy_mask_cache(0) + , legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , - cpu_set_cache(nullptr), - cpu_set_valid(false) + , cpu_set_cache(nullptr) + , cpu_set_valid(false) #endif #if __APPLE__ - , - legacy_policy_cache(0), - legacy_policy_valid(false) + , legacy_policy_cache(0) + , legacy_policy_valid(false) #endif { copy_from(other); @@ -2781,12 +2784,30 @@ ULONG_PTR CpuSet::get_legacy_mask() const if (!use_extended) { // Fast path: directly use fast_mask (truncated to ULONG_PTR size) - legacy_mask_cache = (ULONG_PTR)(fast_mask & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + if (sizeof(ULONG_PTR) >= sizeof(uint64_t)) + { + legacy_mask_cache = (ULONG_PTR)fast_mask; + } + else + { + // Create mask for ULONG_PTR size without undefined behavior + const uint64_t ptr_mask = (sizeof(ULONG_PTR) == 4) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + legacy_mask_cache = (ULONG_PTR)(fast_mask & ptr_mask); + } } else if (extended_mask && extended_capacity > 0) { // Extended path: use first word, truncated to ULONG_PTR size - legacy_mask_cache = (ULONG_PTR)(extended_mask[0] & ((1ULL << (sizeof(ULONG_PTR) * 8)) - 1)); + if (sizeof(ULONG_PTR) >= sizeof(uint64_t)) + { + legacy_mask_cache = (ULONG_PTR)extended_mask[0]; + } + else + { + // Create mask for ULONG_PTR size without undefined behavior + const uint64_t ptr_mask = (sizeof(ULONG_PTR) == 4) ? 0xFFFFFFFFULL : 0xFFFFFFFFFFFFFFFFULL; + legacy_mask_cache = (ULONG_PTR)(extended_mask[0] & ptr_mask); + } } legacy_mask_valid = true; diff --git a/src/cpu.h b/src/cpu.h index 8d77b2736..1a13636bc 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -5,7 +5,6 @@ #define NCNN_CPU_H #include -#include #if defined _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/src/platform.h.in b/src/platform.h.in index 6ee6a497c..cd45ce4fd 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -4,7 +4,12 @@ #ifndef NCNN_PLATFORM_H #define NCNN_PLATFORM_H +// Ensure basic integer types are available in all modes +#if NCNN_SIMPLESTL +#include +#else #include +#endif #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING From cc76653c2ab4a4449f485e58f3666291fe4f2cd9 Mon Sep 17 00:00:00 2001 From: yok7 <145077166+yok7@users.noreply.github.com> Date: Thu, 17 Jul 2025 06:54:20 +0000 Subject: [PATCH 20/26] apply code-format changes --- src/cpu.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 01f42685e..4a7cda818 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -2425,41 +2425,41 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0) - , extended_mask(nullptr) - , extended_capacity(0) - , use_extended(false) + : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) #if defined _WIN32 - , legacy_mask_cache(0) - , legacy_mask_valid(false) + , + legacy_mask_cache(0), + legacy_mask_valid(false) #endif #if defined __ANDROID__ || defined __linux__ - , cpu_set_cache(nullptr) - , cpu_set_valid(false) + , + cpu_set_cache(nullptr), + cpu_set_valid(false) #endif #if __APPLE__ - , legacy_policy_cache(0) - , legacy_policy_valid(false) + , + legacy_policy_cache(0), + legacy_policy_valid(false) #endif { copy_from(other); From 6105437380017d3670ccf2bb1a28eb18db95e796 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 20:21:31 +0800 Subject: [PATCH 21/26] Fix Windows ARM compatibility for popcount64 function - Add architecture-specific conditional compilation for __popcnt64 - __popcnt64 is only available on x86/x64, not on ARM architectures - Use fallback implementation for ARM and other non-x86 architectures - Resolves LNK2019 unresolved external symbol error on Windows ARM builds - Maintains performance on x86/x64 while ensuring compatibility across all platforms --- src/cpu.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 4a7cda818..c29dd2fca 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -2675,10 +2675,11 @@ static int popcount64(uint64_t x) { #if defined(__GNUC__) || defined(__clang__) return __builtin_popcountll(x); -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + // __popcnt64 is only available on x86/x64, not on ARM return (int)__popcnt64(x); #else - // Fallback implementation + // Fallback implementation for ARM and other architectures int count = 0; while (x) { From b6779e8d11e44b6dfc30402fc1eafc85f836dbb4 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 20:53:54 +0800 Subject: [PATCH 22/26] Fix compilation and test issues - Fix C++03 compatibility by using instead of - Fix get_big_cpu_count() to return 0 when no big cores detected - Resolves multiheadattention test failures caused by thread count changes - Ensures compatibility with simplestl-simplemath mode --- src/cpu.cpp | 3 +-- src/platform.h.in | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index c29dd2fca..672fc57ab 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -3482,8 +3482,7 @@ int get_little_cpu_count() int get_big_cpu_count() { try_initialize_global_cpu_info(); - int big_cpu_count = get_cpu_thread_affinity_mask(2).num_enabled(); - return big_cpu_count ? big_cpu_count : g_cpucount; + return get_cpu_thread_affinity_mask(2).num_enabled(); } int get_physical_cpu_count() diff --git a/src/platform.h.in b/src/platform.h.in index cd45ce4fd..dfbe1830e 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -5,11 +5,8 @@ #define NCNN_PLATFORM_H // Ensure basic integer types are available in all modes -#if NCNN_SIMPLESTL +// Use C header for compatibility with C++03 and simple modes #include -#else -#include -#endif #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING From a356f6ef3f0db52500f5f64991ffae96b6febd7b Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Thu, 17 Jul 2025 21:04:36 +0800 Subject: [PATCH 23/26] Fix aarch64-native simplestl-simplemath compilation - Use stdint.h consistently for all modes to avoid C++03/C++11 conflicts - Prevents vector template conflicts between standard library and simplestl - Resolves 'wrong number of template arguments' errors in aarch64-native CI --- src/platform.h.in | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform.h.in b/src/platform.h.in index dfbe1830e..2753fabbd 100644 --- a/src/platform.h.in +++ b/src/platform.h.in @@ -4,9 +4,7 @@ #ifndef NCNN_PLATFORM_H #define NCNN_PLATFORM_H -// Ensure basic integer types are available in all modes -// Use C header for compatibility with C++03 and simple modes -#include + #cmakedefine01 NCNN_STDIO #cmakedefine01 NCNN_STRING @@ -317,6 +315,9 @@ static inline void swap_endianness_32(void* x) #if NCNN_SIMPLESTL #include "simplestl.h" #else +// Ensure basic integer types are available when not using simplestl +// Use C header for compatibility with C++03 and simple modes +#include #include #include #include From 1b3bb3fa9605f24d49b56f2fa07ce0e72f44867b Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Wed, 23 Jul 2025 14:15:03 +0800 Subject: [PATCH 24/26] Fix popcount64 linking issue and improve compatibility - Fix undefined reference to __popcountdi2 by adding __POPCNT__ check - Use Brian Kernighan's algorithm for better fallback performance - Improve C compatibility by using NULL instead of nullptr - Use stdint.h instead of cstdint for better C compatibility - Prioritize MSVC __popcnt64 over GCC builtin for better reliability This resolves linking errors in environments where compiler builtins are not properly linked, particularly affecting test compilation. --- src/cpu.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 672fc57ab..e73e2fd98 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -16,7 +16,7 @@ #if !NCNN_SIMPLESTL #include -#include +#include #include #include #endif @@ -1775,7 +1775,7 @@ static void initialize_cpu_thread_affinity_mask(ncnn::CpuSet& mask_all, ncnn::Cp if (glpie != NULL) { DWORD bufferSize = 0; - glpie(RelationProcessorCore, nullptr, &bufferSize); + glpie(RelationProcessorCore, NULL, &bufferSize); std::vector buffer(bufferSize); if (!glpie(RelationProcessorCore, (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(buffer.data()), &bufferSize)) { @@ -2425,7 +2425,7 @@ namespace ncnn { // New unified CpuSet implementation supporting >64 CPUs CpuSet::CpuSet() - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0), extended_mask(NULL), extended_capacity(0), use_extended(false) #if defined _WIN32 , legacy_mask_cache(0), @@ -2433,7 +2433,7 @@ CpuSet::CpuSet() #endif #if defined __ANDROID__ || defined __linux__ , - cpu_set_cache(nullptr), + cpu_set_cache(NULL), cpu_set_valid(false) #endif #if __APPLE__ @@ -2445,7 +2445,7 @@ CpuSet::CpuSet() } CpuSet::CpuSet(const CpuSet& other) - : fast_mask(0), extended_mask(nullptr), extended_capacity(0), use_extended(false) + : fast_mask(0), extended_mask(NULL), extended_capacity(0), use_extended(false) #if defined _WIN32 , legacy_mask_cache(0), @@ -2453,7 +2453,7 @@ CpuSet::CpuSet(const CpuSet& other) #endif #if defined __ANDROID__ || defined __linux__ , - cpu_set_cache(nullptr), + cpu_set_cache(NULL), cpu_set_valid(false) #endif #if __APPLE__ @@ -2494,7 +2494,7 @@ void CpuSet::copy_from(const CpuSet& other) if (extended_mask) { free(extended_mask); - extended_mask = nullptr; + extended_mask = NULL; } extended_capacity = 0; @@ -2522,7 +2522,7 @@ void CpuSet::copy_from(const CpuSet& other) if (cpu_set_cache) { CPU_FREE(cpu_set_cache); - cpu_set_cache = nullptr; + cpu_set_cache = NULL; } #endif #if __APPLE__ @@ -2673,18 +2673,20 @@ bool CpuSet::is_enabled(int cpu) const // Helper function to count bits in a 64-bit integer static int popcount64(uint64_t x) { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_popcountll(x); -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) // __popcnt64 is only available on x86/x64, not on ARM return (int)__popcnt64(x); +#elif (defined(__GNUC__) || defined(__clang__)) && defined(__POPCNT__) && !defined(__FREESTANDING__) && !NCNN_SIMPLESTL + // Only use builtin if POPCNT instruction is available + return __builtin_popcountll(x); #else - // Fallback implementation for ARM and other architectures + // Fallback implementation for compatibility + // Use Brian Kernighan's algorithm for better performance int count = 0; while (x) { - count += x & 1; - x >>= 1; + x &= x - 1; // Clear the lowest set bit + count++; } return count; #endif @@ -2842,7 +2844,7 @@ const cpu_set_t* CpuSet::get_cpu_set() const { cpu_set_cache = CPU_ALLOC(CPU_SETSIZE); if (!cpu_set_cache) - return nullptr; + return NULL; } CPU_ZERO_S(CPU_ALLOC_SIZE(CPU_SETSIZE), cpu_set_cache); From cfb422145d720d603c6157e0f89a0f1dd128afe1 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Fri, 25 Jul 2025 00:46:38 +0800 Subject: [PATCH 25/26] Add GitHub Actions CI for >64 CPU support testing - Add Windows MSVC build and test workflow - Add Linux build and test workflow - Test popcount64 linking issues - Validate >64 CPU support across platforms --- .github/workflows/linux-high-cpu-test.yml | 37 ++++++++++++++++++++ .github/workflows/windows-high-cpu-test.yml | 38 +++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 .github/workflows/linux-high-cpu-test.yml create mode 100644 .github/workflows/windows-high-cpu-test.yml diff --git a/.github/workflows/linux-high-cpu-test.yml b/.github/workflows/linux-high-cpu-test.yml new file mode 100644 index 000000000..49f2e1e6b --- /dev/null +++ b/.github/workflows/linux-high-cpu-test.yml @@ -0,0 +1,37 @@ +name: Linux >64 CPU Support Test + +on: + push: + branches: [ feature/support-64plus-cpu ] + pull_request: + branches: [ master ] + +jobs: + linux-build-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake + + - name: Build NCNN + run: | + mkdir build + cd build + cmake -DNCNN_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release .. + make -j$(nproc) + + - name: Test CPU functionality + run: | + cd build + ./tests/test_cpu + + - name: Run comprehensive tests + run: | + cd build + ctest --output-on-failure --parallel $(nproc) diff --git a/.github/workflows/windows-high-cpu-test.yml b/.github/workflows/windows-high-cpu-test.yml new file mode 100644 index 000000000..b509069e7 --- /dev/null +++ b/.github/workflows/windows-high-cpu-test.yml @@ -0,0 +1,38 @@ +name: Windows >64 CPU Support Test + +on: + push: + branches: [ feature/support-64plus-cpu ] + pull_request: + branches: [ master ] + +jobs: + windows-build-test: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup MSVC + uses: microsoft/setup-msbuild@v2 + + - name: Build NCNN with MSVC + run: | + mkdir build-msvc + cd build-msvc + cmake -G "Visual Studio 17 2022" -A x64 -DNCNN_BUILD_TESTS=ON .. + cmake --build . --config Release --parallel 4 + + - name: Test popcount64 linking + run: | + cd build-msvc + if (Test-Path "tests/Release/test_cpu.exe") { + echo "✓ test_cpu.exe compiled successfully" + .\tests\Release\test_cpu.exe + } + + - name: Run critical tests + run: | + cd build-msvc + ctest -C Release --output-on-failure -R "test_cpu|test_mat" --parallel 2 From aa587fdbf480e568eb299ddc49c259c357f02d98 Mon Sep 17 00:00:00 2001 From: KRT <2386145092@qq.com> Date: Fri, 25 Jul 2025 00:51:23 +0800 Subject: [PATCH 26/26] Add CPU support test workflow for >64 CPU validation - Test Windows MSVC build and popcount64 linking - Test Linux build and comprehensive test suite - Validate >64 CPU support across platforms --- .github/workflows/cpu-support-test.yml | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/cpu-support-test.yml diff --git a/.github/workflows/cpu-support-test.yml b/.github/workflows/cpu-support-test.yml new file mode 100644 index 000000000..0e8761a0d --- /dev/null +++ b/.github/workflows/cpu-support-test.yml @@ -0,0 +1,53 @@ +name: CPU Support Test + +on: + push: + branches: [ feature/support-64plus-cpu ] + pull_request: + branches: [ master ] + +jobs: + windows-test: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup MSVC + uses: microsoft/setup-msbuild@v2 + - name: Build NCNN + run: | + mkdir build + cd build + cmake -G "Visual Studio 17 2022" -A x64 -DNCNN_BUILD_TESTS=ON .. + cmake --build . --config Release --parallel 4 + - name: Test CPU functionality + run: | + cd build + if (Test-Path "tests/Release/test_cpu.exe") { + echo "✓ test_cpu.exe compiled successfully" + .\tests\Release\test_cpu.exe + } + - name: Test popcount64 linking + run: | + cd build + ctest -C Release --output-on-failure -R "test_cpu" --parallel 2 + + linux-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y build-essential cmake + - name: Build NCNN + run: | + mkdir build + cd build + cmake -DNCNN_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release .. + make -j$(nproc) + - name: Test CPU functionality + run: | + cd build + ./tests/test_cpu + - name: Run tests + run: | + cd build + ctest --output-on-failure --parallel $(nproc)