iOS随机数算法及引发的性能研究

因业务需要,需要构造一个算法在iOS端产生6位随机数,实现比较简单,随手封装了2种实现,却意外发现2种实现实际的执行效率与猜测不符,引发相关探究…

  • 知识储备(相关随机数函数)

    1. rand()random()实际并不是一个真正的伪随机数发生器,在使用之前需要先初始化随机种子,否则每次生成的随机数一样。

    2. arc4random()是一个真正的伪随机算法,不需要生成随机种子,因为第一次调用的时候就会自动生成。这个函数生成的随机数范围比较大(它的范围是rand()的两倍),需要用取模的算法对随机值进行限制。

    3. arc4random_uniform(x),可以用来产生0~(x-1)范围内的随机数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      srand((unsigned)time(0)); //不加这句每次产生的随机数不变
      int i = rand() % 5;
      srandom(time(0));
      int i = random() % 5;
      int i = arc4random() % 5 ;
      int i = arc4random_uniform(5);
  • 产生背景:因为业务需要,需要构造一个算法在iOS端产生随机数,研究了相关实现,封装了以下2种实现:

    1. 看着好看的

      1
      2
      3
      4
      5
      6
      7
      8
      + (NSString *)createRandomString:(int)length{
      if (length <= 0) {
      return @"";
      }
      NSUInteger result = arc4random_uniform(pow(10, length));
      NSString *resultString = [NSString stringWithFormat:@"%0*lu",length,(unsigned long)result];
      return resultString;
      }

    2. 看着不好看的

      1
      2
      3
      4
      5
      6
      7
      8
      9
      + (NSString *)createRandomString:(NSUInteger)length{
      if (length <= 0) {
      return @"";
      }
      char cradom[length];
      for (int i = 0; i < length; cradom[i++] = '0' + arc4random_uniform(9))
      ;
      return [[NSString alloc] initWithBytes:cradom length:length encoding:NSUTF8StringEncoding];
      }
  • 猜测效率:初看发现第二种实现里面有for循环会更慢,顺手写了个测试用例,2者分别在iphone6上执行10w次(参数传6-生成6位随机数),结果猜测错误:

    1
    2
    3
    4
    第一种方案结果:
    Test Case 'xxx' measured [Time, seconds] average: 0.310, relative standard deviation: 1.574%, values: [0.318568, 0.308472, 0.308379, 0.310920, 0.308342, 0.314369, 0.316341, 0.304675, 0.303726, 0.304600],
    第二种方案结果:
    Test Case 'xxx' measured [Time, seconds] average: 0.090, relative standard deviation: 20.541%, values: [0.144661, 0.083284, 0.083459, 0.083986, 0.083396, 0.083264, 0.083467, 0.083202, 0.083524, 0.082840],

    既然猜测错误,那么开始查找原因,代码相当简单,猜测是stringWithFormat那行或者arc4random_uniform()函数引起,分别注释2者查看执行时间:

    1
    2
    3
    4
    注释掉arc4random_uniform()函数:
    Test Case 'xxx' measured [Time, seconds] average: 0.277, relative standard deviation: 3.134%, values: [0.302329, 0.272975, 0.272465, 0.275769, 0.273284, 0.272742, 0.275212, 0.274216, 0.272888, 0.273297],
    注释掉stringWithFormat函数:
    Test Case 'xxx' measured [Time, seconds] average: 0.022, relative standard deviation: 26.238%, values: [0.039476, 0.024348, 0.020075, 0.020054, 0.019855, 0.020028, 0.019932, 0.019763, 0.019982, 0.019884],

    问题定位成功,是函数stringWithFormat那行引起的,结论如下:

    stringWithFormat/initWithFormat本身不是很耗性能,但如果在for循环中,特别循环次数比较多的时候,可以考虑用C函数来提高相关性能(sprintf、snprintf 、 asprintf ),修改后的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    + (NSString *)createRandomString:(NSUInteger)length{
    if (length <= 0) {
    return @"";
    }
    NSUInteger result = arc4random_uniform(pow(10, length));
    char *buffer = (char *) malloc(length);
    sprintf(buffer, "%0*lu", (int)length, (unsigned long)result);
    NSString *resultString = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
    free(buffer);
    return resultString;
    }

    性能测试:

    1
    Test Case 'xxx' measured [Time, seconds] average: 0.148, relative standard deviation: 3.707%, values: [0.139766, 0.152092, 0.145130, 0.141498, 0.148388, 0.157696, 0.143160, 0.154644, 0.148861, 0.150025],

    大概性能提升了2倍多,还是比第二种实现的性能差些。malloc和free耗费了一定时间,继续努力:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    + (NSString *)createRandomString:(NSUInteger)length{
    if (length <= 0) {
    return @"";
    }
    NSUInteger result = arc4random_uniform(pow(10, length));
    char cString[length];
    sprintf (cString, "%0*lu", (int)length,(unsigned long)result);
    NSString* resultString = [[NSString alloc] initWithUTF8String:cString];
    return resultString;
    }

    性能测试:

    1
    Test Case 'xxx' measured [Time, seconds] average: 0.078, relative standard deviation: 2.616%, values: [0.084383, 0.078385, 0.077887, 0.077609, 0.077381, 0.077292, 0.077276, 0.077900, 0.077413, 0.077609],

    性能比第二种稍高点,也在我们程序的接受范围内了,那就先这样吧!

参考资料

iOS NSString stringWithFormat: Performance (Objective-C).

why NSString stringWithFormat is slow