目录
? 什么是 False Sharing?
? 伪共享示例代码
? 对比:有伪共享 vs 无伪共享
? 输出示例(在多核机器上)
? 如何避免 False Sharing?
? 总结
首页 后端开发 C++ C错误的分享示例

C错误的分享示例

Aug 16, 2025 am 10:42 AM
c++

False sharing发生在多个线程修改同一缓存行中的不同变量时,导致缓存失效和性能下降;1. 使用结构体填充使每个变量独占一个缓存行;2. 利用alignas或std::hardware_destructive_interference_size进行内存对齐;3. 优先使用线程本地变量最后合并结果,从而避免伪共享,提升多线程程序性能。

C   false sharing example

在多线程编程中,false sharing(伪共享)是一个常见的性能陷阱。它发生在多个线程修改位于同一缓存行(cache line)中的不同变量时,导致不必要的缓存失效和性能下降。C 中尤其容易出现这个问题,因为开发者对内存布局有直接控制。

C   false sharing example

下面是一个典型的 C false sharing 示例,以及如何修复它。


? 什么是 False Sharing?

现代 CPU 缓存以“缓存行”为单位(通常是 64 字节)加载数据。如果两个变量位于同一个缓存行中,即使它们被不同的线程独立访问,一个线程的写操作也会导致另一个线程的缓存行失效,从而强制重新从内存加载 —— 这就是伪共享。

C   false sharing example

? 伪共享示例代码

#include <thread>
#include <vector>
#include <chrono>
#include <iostream>

alignas(64) struct Counter {
    volatile long value = 0;
};

const int NUM_THREADS = 4;
const long ITERATIONS = 10'000'000;

// 多个线程共享一个数组,但每个线程操作不同元素
std::vector<Counter> counters_bad(NUM_THREADS);

void worker_bad(int tid) {
    for (long i = 0; i < ITERATIONS;   i) {
        counters_bad[tid].value  ;
    }
}

int main() {
    std::vector<std::thread> threads;

    auto start = std::chrono::high_resolution_clock::now();

    for (int i = 0; i < NUM_THREADS;   i) {
        threads.emplace_back(worker_bad, i);
    }

    for (auto& t : threads) {
        t.join();
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    std::cout << "Bad (false sharing): " << duration.count() << " ms\n";
    return 0;
}

⚠️ 虽然每个线程操作的是 counters_bad[tid],但 Counter 结构体只用了 alignas(64) 对单个变量对齐,而 std::vector 中的元素是连续存储的。如果 Counter 没有足够填充,多个 Counter 可能挤在同一个缓存行中,造成伪共享。

但上面代码其实还不够典型,我们来写一个更清晰的对比。

C   false sharing example

? 对比:有伪共享 vs 无伪共享

#include <thread>
#include <vector>
#include <chrono>
#include <iostream>

// 方式1:容易产生 false sharing(变量太近)
struct CounterFalse {
    volatile long a = 0;
    // 没有填充,多个 CounterFalse 可能共用一个缓存行
};

// 方式2:避免 false sharing,手动填充到一个缓存行
struct CounterFixed {
    volatile long a = 0;
    char padding[64 - sizeof(long)]; // 填充到 64 字节
};

const int NUM_THREADS = 4;
const long ITERATIONS = 10'000'000;

// 测试1:False sharing 版本
std::vector<CounterFalse> counters_false(NUM_THREADS);
void worker_false(int tid) {
    for (long i = 0; i < ITERATIONS;   i) {
        counters_false[tid].a  ;
    }
}

// 测试2:无 false sharing 版本
std::vector<CounterFixed> counters_fixed(NUM_THREADS);
void worker_fixed(int tid) {
    for (long i = 0; i < ITERATIONS;   i) {
        counters_fixed[tid].a  ;
    }
}

int main() {
    std::vector<std::thread> threads;

    // 测试 false sharing
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < NUM_THREADS;   i) {
        threads.emplace_back(worker_false, i);
    }
    for (auto& t : threads) t.join();
    auto end = std::chrono::high_resolution_clock::now();
    auto duration1 = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "With false sharing: " << duration1.count() << " ms\n";

    threads.clear();

    // 测试 fixed version
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < NUM_THREADS;   i) {
        threads.emplace_back(worker_fixed, i);
    }
    for (auto& t : threads) t.join();
    end = std::chrono::high_resolution_clock::now();
    auto duration2 = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "Without false sharing: " << duration2.count() << " ms\n";

    return 0;
}

? 输出示例(在多核机器上)

With false sharing: 850 ms
Without false sharing: 220 ms

可以看到,避免伪共享后性能提升显著(4倍左右)。


? 如何避免 False Sharing?

  • 使用填充(padding):确保每个线程访问的变量独占一个缓存行(通常是 64 字节)。
  • 使用 alignas(64):对结构体或变量强制对齐。
  • 每个线程使用本地变量,最后合并:减少共享变量访问。
  • 使用标准库提供的对齐工具(如 C 11 的 alignasstd::hardware_destructive_interference_size

? C 17 引入了两个推荐常量:

  • std::hardware_destructive_interference_size:避免伪共享的最小间隔(通常是缓存行大小)
  • std::hardware_constructive_interference_size:建议共享的大小

示例(C 17 起):

struct alignas(std::hardware_destructive_interference_size) Counter {
    volatile long value = 0;
};

? 总结

  • False sharing 看似无害,实则严重拖慢多线程程序。
  • 当多个线程频繁写入“逻辑上独立但物理上靠近”的变量时,容易触发。
  • 解决方法:内存填充 对齐,确保变量不在同一缓存行。
  • 在性能敏感的并行程序中(如高频计数、并发统计),必须注意此问题。

基本上就这些,不复杂但容易忽略。

以上是C错误的分享示例的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

PHP教程
1585
276
Succinct (PROVE币) 价格预测:2025、2026、2027-2030 年 Succinct (PROVE币) 价格预测:2025、2026、2027-2030 年 Aug 11, 2025 am 10:12 AM

目录什么是Succinct(PROVE)哪些风险投资支持Succinct(PROVE)?Succinct(PROVE)的工作原理SP1zkVM和Prover网络OPSuccinct技术跨链验证PROVE代币经济学代币详情2025、2026、2027-2030年Succinct(PROVE)价格预测Succinct(PROVE)价格预测Succinct(PROVE)价格预测:交易量扩张和上市势头2025年至20

应用程序无法正常启动(0xc0000906)怎么办?解决方案看这里 应用程序无法正常启动(0xc0000906)怎么办?解决方案看这里 Aug 13, 2025 pm 06:42 PM

打开软件或游戏时,突然出现“应用程序无法正常启动(0xc0000906)”的提示,许多用户都会感到困惑,不知从何下手。实际上,这类错误大多源于系统文件损坏或运行库缺失。别急着重装系统,本文为你提供几种简单有效的解决方法,助你快速恢复程序运行。一、0xc0000906错误到底是什么?错误代码0xc0000906属于Windows系统常见的启动异常,通常表示程序在运行时无法加载必要的系统组件或运行环境。该问题常出现在运行大型软件或游戏时,主要原因可能包括:必要的运行库未安装或遭到破坏。软件安装包不完

如何在C中使用正则表达式 如何在C中使用正则表达式 Aug 12, 2025 am 10:46 AM

要使用C 中的正则表达式,需包含头文件,并利用其提供的函数进行模式匹配和文本处理。1.使用std::regex_match进行全字符串匹配,仅当整个字符串符合模式时返回true;2.使用std::regex_search在字符串中查找任意位置的匹配;3.使用std::smatch提取捕获组,通过matches[0]获取完整匹配,matches[1]及后续获取子匹配;4.使用std::regex_replace替换匹配的文本,支持用$1、$2等引用捕获组;5.可在构造regex时添加icase(

计算机中丢失MSVCP71.dll怎样修复 只需三种方法 计算机中丢失MSVCP71.dll怎样修复 只需三种方法 Aug 14, 2025 pm 08:03 PM

电脑提示“计算机中丢失MSVCP71.dll”,通常是因为系统缺少关键运行组件,导致软件无法正常加载。本文将深入解析该文件的功能、报错根源,并提供三种高效解决方案,助你快速恢复程序运行。一、MSVCP71.dll是什么?MSVCP71.dll属于MicrosoftVisualC 2003的核心运行库文件,属于动态链接库(DLL)类型,主要用于支持C 编写的程序调用标准函数、STL模板及基础数据处理模块。许多2000年代初开发的应用程序和经典游戏都依赖此文件运行。一旦该文件缺失或损坏,系

如何在C中获取文件的大小 如何在C中获取文件的大小 Aug 11, 2025 pm 12:34 PM

使用std::ifstream的seekg和tellg方法可跨平台获取文件大小,通过打开二进制文件并定位到末尾,利用tellg()返回字节数;2.C 17及以上推荐使用std::filesystem::file_size,代码简洁且通过异常处理错误,需启用C 17标准;3.在POSIX系统上可使用stat()函数高效获取文件大小,适用于性能敏感场景。应根据编译器和平台选择合适方法,优先使用std::filesystem(若可用),否则使用ifstream保证兼容性,或在Unix系统上使用st

C操作员超载示例 C操作员超载示例 Aug 15, 2025 am 10:18 AM

C 中的运算符重载允许为自定义类型赋予标准运算符新行为,1.通过成员函数重载 返回新对象;2.重载 =修改当前对象并返回引用;3.友元函数重载

如何为C项目编写基本的制作费? 如何为C项目编写基本的制作费? Aug 15, 2025 am 11:17 AM

AbasicMakeFileAutomatesc compilationByByDefindingruleswithtargets和commands.2.KeyComponentsIncludeVariablesLikeCXX,CXXFlags,cxxflags,target,srcs,srcs,srcs,srcs,srcs,objstosimplifyConfiguration.3.AptertNrules.3.aptertnrules(compiles)comptiles $ compiles $:%

如何与C中的STD ::变体一起工作 如何与C中的STD ::变体一起工作 Aug 14, 2025 am 11:32 AM

std::variant是C 17引入的类型安全联合体,能安全地持有多个指定类型之一的值,通过std::get、std::holds_alternative、std::visit和std::get_if等方法实现安全访问与类型检查,结合std::monostate可模拟可选值,推荐使用std::visit进行类型分发并避免大型类型列表以提升可维护性,最终确保类型安全和异常安全。

See all articles