更快的AtomicInteger

感谢同事【空蒙】的投稿

之前看了java8的longadder实现,最近又看到一篇文章介绍longadder实现的。其实现思路也是分段,最后需要get的时候,再进行sum计算。其核心思路就是减少并发,但之前老的Atomic,难道就没有提升的空间了吗?昨晚进行了一次测试。测试代码如下:

/**
* Atomically increments by one the current value.
*
*@return the updated value
*/
public final int incrementAndGet() {

  for(;;) {

   int current = get();

    int next = current + 1;

    if(compareAndSet(current, next))

    return next;

   }
}

以incrementAndGet为例,在非常高的并发下,compareAndSet会很大概率失败,因此导致了此处cpu不断的自旋,对cpu资源的浪费

既然知道此地是高并发的瓶颈,有什么办法呢?

public class AtomicBetter {

      AtomicInteger ai=new AtomicInteger();

      public int incrementAndGet() {

             for(;;) {

                    int current =ai.get();

                    int next = current + 1;

                    if(compareAndSet(current, next))

                           return next;

              }

       }

      /**

       *如果cas失败,线程park

       *@paramcurrent

       *@paramnext

       *@return

       */

      private boolean compareAndSet(intcurrent,intnext) {

             if(ai.compareAndSet(current, next)) {

                    return true;

              }else{

                     LockSupport.parkNanos(1);

                    return false;

              }

       }

}

很简单,当cas失败后,对线程park,减少多线程竞争导致的频繁cas失败,更进一步的导致cpu自旋,浪费cpu的运算能力。在4核虚拟机,Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz  linux 2.6.32,(注意,老版本的内核,不支持高的精度ns级) 进行测试,同样都起4个线程,每个线程里面对AtomicInteger进行5kw次的incrementAndGet。原生的AtomicInteger,耗时14232ms,进行了35870次上下文切换,总共87967770955次时钟周期。那prak 1ns下呢,耗时5195ms,进行了19779次上下文切换,总共36187480878次时钟周期,明显性能上比原生的AtomicInteger更好,那这个park多少合适呢?那就只有人肉测试了

park time(ms) context-switches cycles
AtomicInteger 14232 35,870 87,967,770,955
1ns 5195 19,779 36,187,480,878
10ns 5050 20,223 34,839,351,263
100ns 5238 20,724 37,250,431,417
125ns 4536 47,479 26,149,046,788
140ns 4008 100,022 18,342,728,517
150ns 3864 110,720 16,146,816,453
200ns 3561 125,694 11,793,941,243
300ns 3456 127,072 10,200,338,988
500ns 3410 132,163 9,545,542,340
1us 3376 134,463 9,125,973,290
5us 3383 122,795 9,009,226,315
10us 3367 113,930 8,905,263,507
100us 3391 50,925 8,359,532,733
500us 3456 17,225 8,096,303,146
1ms 3486 10,982 7,993,812,198
10ms 3456 2,600 7,845,610,195
100ms 3555 1,020 7,804,575,756
500ms 3854 822 7,814,209,077

 

alt

 

alt

 

alt

本机环境下,park 1ms下,相对耗时,cs次数来说是最好的。因此这种优化要达到最佳效果,还要看cpu的情况而定,不是一概而定的

两个问题:

1、cas失败对线程park的副作用是什么。

2、如果park的时间继续加大,那会是这么样的结果呢。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 更快的AtomicInteger


FavoriteLoading添加本文到我的收藏
  • Trackback 关闭
  • 评论 (11)
    • CasinW
    • 2014/04/25 3:41下午

    我觉得将park的时间增大后,再看结果还是很有必要的。
    另外我觉得park的时间与实际环境有很大关系,线程个数,操作次数等等,单纯的park某个时间不是很合适,不知道我的想法是否正确。
    如有错误,期待与您交流,我的邮箱:casinww@163.com

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • 我同意您的观点。

      另外一个疑问:

      “本机环境下,park 1ms下,相对耗时,cs次数来说是最好的。因此这种优化要达到最佳效果,还要看cpu的情况而定,不是一概而定的“

      既然和具体的cpu有关,那么这个设计是不是可以理解为非可移植性的呢?(另外,我认为很和并发程度也是相关的,文中没有测试低并发场景下的性能,这样的实验一般是控制读写比例和控制并发线程数目来做,读写比例:比方说20%increment 10%decrement 70%get对比50%increment 50%decrement)

      或者说,如果要做到可移植性,那么需要有公式来计算这个park的时间?

      谢谢!

      // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
  1. 我的疑问是,自旋不就是为了减少上下文切换吗? 为什么park 1ns 后反而上下文次数减少这么多?

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • sunqi
    • 2014/04/29 9:12上午

    周 可人 :
    我同意您的观点。
    另外一个疑问:
    “本机环境下,park 1ms下,相对耗时,cs次数来说是最好的。因此这种优化要达到最佳效果,还要看cpu的情况而定,不是一概而定的“
    既然和具体的cpu有关,那么这个设计是不是可以理解为非可移植性的呢?(另外,我认为很和并发程度也是相关的,文中没有测试低并发场景下的性能,这样的实验一般是控制读写比例和控制并发线程数目来做,读写比例:比方说20%increment 10%decrement 70%get对比50%increment 50%decrement)
    或者说,如果要做到可移植性,那么需要有公式来计算这个park的时间?
    谢谢!

    increment 和 decrement,最下面都是公共的unsafe的cas,不管怎么分配比例,所以其实是一样的,而且increment本身就是int返回值,所以也没有额外再去get的需要,就是get也只是直接返回里面的volatile int value。本身这个调整,就是减少cas时候自旋的开销,这算有些线程主动牺牲一下的雷锋精神吧。这测试和硬件环境与并发数是有很大关联,但应该可选取到一个较合适的park时间,以获取最好效果吧

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • sunqi
    • 2014/04/29 9:13上午

    liuinsect :
    我的疑问是,自旋不就是为了减少上下文切换吗? 为什么park 1ns 后反而上下文次数减少这么多?

    自旋目的,确实是为减少上下文切换,达到自增(减)完成,这在低并发下确实,但线程也受内核的调度,如果高并发下很多线程在自旋,linux2.6.32下,cfs也会动态调度,自然也有线程切换开销。

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • 哦,对,原来脑袋里的印象一直是 自旋,自旋,却没想到自旋的线程本身也是受OS调度的,并且,自旋导致线程的占用时间片增多,在调度算法上 可能还会受到惩罚。 恩,跳出来,又学习到一点东西。

      // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
  2. 你这种方式性能提升了一倍?从开头那文章里给出的性能对比数据看,你这种方式性能提升的并不算多

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • zankard
    • 2014/07/28 5:20下午

    sun lab里有人针对CAS的性能做过调整测试,还写了篇论文,其中一个方法就是文章使用的,在cas失败时进行一定时间的等待。
    论文里,也没有详细的解释这个做法能起到效果的原因。说了一些,觉得大概意思是说多核cpu,在高并发的情况下,cas失败率会显著提升,这些也会增加因为要保持各个处理器的缓存,主存一致性同步的操作,而这些失败的cas去取无效的值,也没什么意义,同时也会导致成功的cas变慢。那么,适当地降低并发,就能达到效果。
    至于参数,他们也还没有找到平台独立的策略。
    论文链接
    Lightweight Contention Management for Efficient Compare-and-Swap Operations
    https://blogs.oracle.com/dave/entry/lightweight_contention_management_for_efficient

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • newer
    • 2014/08/04 2:34下午

    这个测试数据是怎么获取

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • newer
    • 2014/08/05 4:09下午

    求回复,测试数据怎么抓取出来的??有github地址么

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
    • m5jun
    • 2016/06/15 7:58上午

    return next;

    这里错了是return current;

    // Customize your functions function reset_password_message( $message, $key ) { if ( strpos($_POST['user_login'], '@') ) { $user_data = get_user_by('email', trim($_POST['user_login'])); } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } $user_login = $user_data->user_login; $msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n"; $msg .= network_site_url() . "\r\n\r\n"; $msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n"; $msg .= __('若这不是您本人要求的,请忽略本邮件。') . "\r\n\r\n"; $msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n"; $msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ; return $msg; } add_filter('retrieve_password_message', reset_password_message, null, 2);
您必须 登陆 后才能发表评论

return top