天天看點

性能測試中的随機數性能問題探索緣起多線程單線程末了

在軟體測試中,經常會遇到随機數。我簡單分成了兩類:

  1. 簡單取随機數;
  2. 從一個集合中随機取值。

其實第二個場景包含在第一個場景内。對于接口測試來說,通常我們直接使用第二種場景比較多,就是從某一個集合中随機取一個值。如果更複雜一些,每個值擁有不同的權重,其中這個也可以轉化成第二個場景來說。

緣起

為什麼要把第二個場景和第一個場景分開呢,這個問題源于之前寫過的文章ConcurrentHashMap性能測試,當時發現自己封裝的

com.funtester.frame.SourceCode#random(java.util.List<F>)

方法性能存在瓶頸,特别消耗CPU資源。

雖然單機QPS也在50萬+,但是因為這個方法很多地方都會用到,是以還是想提升一些性能。是以我就搜尋了一些高性能随機數的功能,跟我之前搜到的資料一緻,使用

java.util.concurrent.ThreadLocalRandom

這個實作類是性能最高的,方法如下:

/**
     * 擷取随機數,擷取1~num 的數字,包含 num
     *
     * @param num 随機數上限
     * @return 随機數
     */
    public static int getRandomInt(int num) {
        return ThreadLocalRandom.current().nextInt(num) + 1;
    }

           

針對第二種場景,還有一種實作思路:通過循環去集合中取即可。就是順序去取,而不是每次都從集合中随機。

舉個例子,我們有10萬測試使用者進行流量回放,示範代碼如下:

def funtest = {
            random(drivers).getGetResponse(random(urls))
        }
        new FunQpsConcurrent(funtest).start()

           

這裡調用了兩次

com.funtester.frame.SourceCode#random(java.util.List<F>)

,當QPS到達10萬級别時候,理論上這個方法導緻的瓶頸還是有一些影響的。

多線程

是以我用了新思路進行改造,下面是兩種思路的對比壓測用例,這個測試用例裡面其實有三個實作:

  1. random
  2. AtomicInteger
  3. int

用例如下:

package com.funtest.groovytest

import com.funtester.base.constaint.FixedThread
import com.funtester.frame.SourceCode
import com.funtester.frame.execute.Concurrent

import java.util.concurrent.atomic.AtomicInteger

class FunTest extends SourceCode {

    static int times = 1000

    static int thread = 500

    static def integers = 0..100 as List

    static def integer = new AtomicInteger()

    static def i = 0

    static def size = integers.size()

    public static void main(String[] args) {
        RUNUP_TIME = 0
        new Concurrent(new FunTester(), thread, "測試随機數性能").start()
    }

    private static class FunTester extends FixedThread {


        FunTester() {
            super(null, times, true)
        }

        @Override
        protected void doing() throws Exception {
                        10000.times {random(integers)}
//            10000.times {integers.get(integer.getAndIncrement() % size)}
//                        10000.times {integers.get(i++ % size)}
        }

        @Override
        FunTester clone() {
            return new FunTester()
        }
    }

}
           

由于測試中均達到了CPU硬體瓶頸,相同參數情況下結論比較明顯,就沒有進行多輪的對比測試。下面分享一下測試結果:

  1. random:1151
  2. AtomicInteger:3152
  3. int:2273

沒想到用了

java.util.concurrent.atomic.AtomicInteger

反而性能更高了,這個問題略微有點深奧,暫時沒有思路。

單線程

下面我們來測試一下單線程的性能,下面是我的用例:

package com.funtest.groovytest


import com.funtester.frame.SourceCode

import java.util.concurrent.atomic.AtomicInteger

class FunTestT extends SourceCode {

    static int times = 1000000

    static def integers = 0..100 as List

    static def integer = new AtomicInteger()

    static def i = 0

    static def size = integers.size()

    public static void main(String[] args) {

        time {
            //            times.times {random(integers)}
            //                        times.times {integers.get(integer.getAndIncrement() % size)}
            times.times {integers.get(i++ % size)}
        } , "随機數性能測試"

    }


}
           

下面是測試結果,這裡我記錄了執行完所有循環次數的時間,機關是ms(毫秒)。

  1. random:763
  2. AtomicInteger:207
  3. int:270

這下結論明确了,就

java.util.concurrent.atomic.AtomicInteger

了。

末了

/**
     * 随機選擇某個對象
     *
     * @param list
     * @param index 自增索引
     * @param <F>
     * @return
     */
    public static <F> F random(List<F> list, AtomicInteger index) {
        if (list == null || list.isEmpty()) ParamException.fail("數組不能為空!");
        return list.get(index.getAndIncrement() % list.size());
    }
           
  • 性能測試專題
  • Java、Groovy、Go、Python
  • 單測&白盒
  • FunTester社群風采
  • 測試理論雞湯
  • 接口功能測試專題
  • FunTester視訊專題
  • 案例分享:方案、BUG、爬蟲
  • UI自動化專題
  • 測試工具專題

繼續閱讀