zoukankan      html  css  js  c++  java
  • 由一道淘宝面试题到False sharing问题


    今天在看淘宝之前的一道面试题目,内容是

    在高性能服务器的代码中经常会看到类似这样的代码:

    typedef union
    {
      erts_smp_rwmtx_t rwmtx;
      byte cache_line_align_[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(erts_smp_rwmtx_t))];
    }erts_meta_main_tab_lock_t;
    erts_meta_main_tab_lock_t main_tab_lock[16];


    请问其中用来填充的cache_line_align的作用是?

    之前有学习到c语言中宏align是内存补齐的作用,那这个不就是cache line补齐?但是啥是cache line??为啥有这么一步?

    1.首先,什么是cache line?

    CPU处理指令时,由于“Locality of Reference”原因,需要决定哪些数据需要加载到CPU的缓存中,以及如何预加载。因为不同的处理器有不同的规范,导致这部分工作具有不确定性。在加载的过程中,涉及到一个非常关键的术语:cache line。

    cache line是能被cache处理的内存chunks,chunk的大小即为cache line size,典型的大小为32,64及128 bytes. cache能处理的内存大小除以cache line size即为cache line。

    了解了cache line,然后再熟悉一下cpu上cache的一些策略

    2.cpu上cache的策略

    cache entry (cache条目)
    包含如下部分
    1) cache line : 从主存一次copy的数据大小)
    2) tag : 标记cache line对应的主存的地址
    3) falg : 标记当前cache line是否invalid, 如果是数据cache, 还有是否dirty

    cpu访问主存的规律
    1) cpu从来都不直接访问主存, 都是通过cache间接访问主存
    2) 每次需要访问主存时, 遍历一遍全部cache line, 查找主存的地址是否在某个cache line中.
    3) 如果cache中没有找到, 则分配一个新的cache entry, 把主存的内存copy到cache line中, 再从cache line中读取.

    cache中包含的cache entry条目有限, 所以, 必须有合适的cache淘汰策略
    一般使用的是LRU策略.
    将一些主存区域标记为non-cacheble, 可以提高cache命中率, 降低没用的cache

    回写策略
    cache中的数据更新后,需要回写到主存, 回写的时机有多种
    1) 每次更新都回写. write-through cache
    2) 更新后不回写,标记为dirty, 仅当cache entry被evict时才回写
    3) 更新后, 把cache entry送如回写队列, 待队列收集到多个entry时批量回写.

    cache一致性问题
    有两种情况可能导致cache中的数据过期
    1) DMA, 有其他设备直接更新主存的数据
    2) SMP, 同一个cache line存在多个CPU各自的cache中. 其中一个CPU对其进行了更新.

    3.为啥需要cache line 补齐呢?

    让我们先看一个例子,

    举例:

    // 如下代码在SMP环境下存在cache频繁刷新问题
    double sum=0.0, sum_local[NUM_THREADS];
    #pragma omp parallel num_threads(NUM_THREADS)
    {
     int me = omp_get_thread_num();
     sum_local[me] = 0.0;
    
     #pragma omp for
     for (i = 0; i < N; i++)
     sum_local[me] += x[i] * y[i];
    
     #pragma omp atomic
     sum += sum_local[me];
    }
        因为sum_local数组是个全局变量, 多个线程都会访问, 并且, 各个线程访问的地方很接近, 会导致一个线程更新, 其他CPU的cache line失效.
        所以在尽量不要让更新频率非常高(例如,计数器)和经常访问的变量分布在同一个cache line中,以避免“cache ping-pong”,亦“false sharing”现象。
          OK,为啥需要补齐呢,上面的例子里面多个线程的访问会出现false sharing现象,如果服务器采用这样的,则服务器性能会严重影响,为了解决这个问题,最简单的办法是采用cache line 补齐的方法。

    ps:在查找这个面试题的时候,有意思的是我在淘宝核心系统团队博客上发现了对这个题目的解答,我觉得简答的不是很认真,他们是参考一篇外文文献《Avoiding and Identifying False Sharing Among Threads》,这篇文章主要解决在SMP环境下cache line被频繁刷新的的问题。所以只是简单的将大意翻译过来。
    将复制过来:

    在做多线程程序的时候,为了避免使用锁,我们通常会采用这样的数据结构:根据线程的数目,安排一个数组, 每个线程一个项,互相不冲突. 从逻辑上看这样的设计无懈可击,但是实践的过程我们会发现这样并没有提高速度. 问题在于cpu的cache line. 我们在读主存的时候,数据同时被读到L1,L2中去,而且在L1中是以cache line(通常64)字节为单位的. 每个Core都有自己的L1,L2,所以每个线程在读取自己的项的时候, 也把别人的项读进去, 所以在更新的时候,为了保持数据的一致性, core之间cache要进行同步, 这个会导致严重的性能问题. 这就是所谓的False sharing问题, 有兴趣的同学可以wiki下.


    解决方法很简单:
    把每个项凑齐cache line的长度,实现隔离.

    1
    2
    3
    4
    5
    6
    7
    8
    typedef union {
         erts_smp_rwmtx_t rwmtx;
         byte cache_line_align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(
                     sizeof (erts_smp_rwmtx_t))];
    } erts_meta_main_tab_lock_t;
    或者
    _declspec (align(64)) int thread1_global_variable;
    __declspec (align(64)) int thread2_global_variable;

    这就是为什么在高性能服务器中到处看到cache_line_align, 号称是避免cache的trash.

    类似valgrind和intel vtune的工具可以做这个层次的性能微调.

    (淘宝核心系统博客: http://rdc.taobao.com/blog/cs/?p=523

     

     

       

  • 相关阅读:
    HTML5之viewport使用
    css position小结
    图片预加载
    ie6 双边距问题
    json化的必要性
    nginx配置详解(转)
    nginx技术分享 (转)
    js 解决图片居中问题
    NGUI panel使用soft clip时,屏幕缩放后无法正常工作的问题解决
    使用代码修改camera.cullingMask
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3306473.html
Copyright © 2011-2022 走看看