加快模具滚动模拟

时间:2017-02-03 05:13:25

标签: python probability

我正在模拟无限序列的掷骰子以计算平均时间"击中时间"一个序列。在这种特殊情况下,我正在寻找第一次出现的" 11"或者" 12"。例如在" 34241213113 ..."第一次出现" 12是在时间6和#34; 11"是时间10.这是我的python代码。

import numpy as np

NN=1000000
t11=np.zeros(NN)
t12=np.zeros(NN)

for i in range(NN):
    prev=np.random.randint(1,7)
    flag11=True
    flag12=True
    ctr=2
    while flag11 or flag12:
        curr=np.random.randint(1,7)
        if flag11 and prev==1 and curr==1:
            t11[i]=ctr
            flag11=False
        if flag12 and prev==1 and curr==2:
            t12[i]=ctr
            flag12=False
        ctr=ctr+1;
        prev=curr
print('Mean t11:  %f' %(np.mean(t11)))
print('\nMean t12: %f' %(np.mean(t12)))

一旦观察到两个序列,我们就开始一个新的样本。在预期值收敛到理论值之前需要大约一百万个样本路径(" 11&#34为42;对于" 12"为36)。代码大约需要一分钟才能运行。我是python的新手,并且已经使用了大约一个月。

我想知道是否有办法加速代码,可能是不同的算法,或者可能优化例程?它在编译语言和解释语言上的表现会有明显不同吗?我是

4 个答案:

答案 0 :(得分:1)

您可以通过每次调用numpy(随机块而不是单个值)执行更多工作来加快速度,并使用内置的bytes扫描简化模式搜索:< / p>

import numpy as np

NN=1000000
t11=np.zeros(NN)
t12=np.zeros(NN)

for i in range(NN):
    block = b'\xff'  # Prepop w/garbage byte so first byte never part of cnt
    flag11 = flag12 = True
    ctr = 1  # One lower to account for non-generated first byte

    while flag11 or flag12:
        # Generate 100 numbers at once, much faster than one at a time,
        # store as bytes for reduced memory and cheap searches
        # Keep last byte of previous block so a 1 at end matches 1/2 at beginning of next
        block = block[-1:] + bytes(np.random.randint(1, 7, 100, np.uint8))

        # Containment test scans faster in C than Python level one-at-a-time check
        if flag11 and b'\x01\x01' in block:
            t11[i] = ctr + block.index(b'\x01\x01')
            flag11 = False
        if flag12 and b'\x01\x02' in block:
            t12[i] = ctr + block.index(b'\x01\x02')
            flag12 = False
        ctr += 100
print('Mean t11:  %f' %(np.mean(t11)))
print('\nMean t12: %f' %(np.mean(t12)))

在我的(无可置疑的动力不足的机器)上,您的原始代码需要大约96秒才能运行;我的优化版本耗时约6.6秒,约为原始运行时的7%。即使假设(平均)超过一半的随机生成是不需要的,当它避免更多Python级别的工作循环并再次尝试时,它仍然更快。

进一步重写,您可以通过更改以下内容来避免block的双重扫描:

        if flag11 and b'\x01\x01' in block:
            t11[i] = ctr + block.index(b'\x01\x01')
            flag11 = False

更冗长,但效率更高:

    if flag11:
        try:
            t11[i] = ctr + block.index(b'\x01\x01')
        except ValueError:
            pass
        else:
            flag11 = False

(并对flag12测试进行等效更改)

由于生成的前100个字节通常都有命中,这意味着您用一个替换两个扫描,并将整个运行时间减少到~6.0秒。有更多的极端微优化可用(更多的是关于了解CPython的内部结构而不是任何逻辑改进),可以在我的机器上将其降低到~5.4秒,但是它们很难看,并且在99.9%的时间内不值得打扰

答案 1 :(得分:1)

这个问题有一个很好的工具:有限状态机。

但是Python并不是快速实现的语言。

这是一个非确定性状态机,可识别任何输入流中的两个序列。 *表示3到6的投掷:

NFSM

这很难实现,因为它一次可以占用多个状态,但是有一种称为子集构造的标准算法将其转换为确定性状态机,这是实施起来非常有效。在此处应用产生这个:

DFSM

这是一个C实现。在Python中,您将使用一个映射,将当前状态的元组加上输入数字转换为下一个状态。在这里,我们使用goto根据执行代码中的位置实现地图:

#include <stdio.h>
#include <stdlib.h>

#define ROLL do { r = 1 + rand() %6; } while (0)

int main(void) {
  int n_trials = 10000000;
  int t_total_11 = 0;
  int t_total_12 = 0;

  for (int n = 0; n < n_trials; n++) {
    int r, t = -1, t_11 = 0, t_12 = 0;
    A:
      ++t;
      ROLL;
      if (r == 1) goto AB;
      goto A;
    AB:
      ++t;
      ROLL;
      if (r == 1) goto ABC;
      if (r == 2) goto AD;
      goto A;
    ABC:
      ++t;
      if (!t_11) {
        t_11 = t;
        t_total_11 += t_11;
        if (t_12) continue;
      }
      ROLL;
      if (r == 1) goto ABC;
      if (r == 2) goto AD;
      goto A;
    AD:
      ++t;
      if (!t_12) {
        t_12 = t;
        t_total_12 += t_12;
        if (t_11) continue;
      }
      ROLL;
      if (r == 1) goto AB;
      goto A;
  }
  printf("Avg for 11: %lf\n", (double) t_total_11 / n_trials);
  printf("Avg for 12: %lf\n", (double) t_total_12 / n_trials);
  return 0;
}

在我原来的Macbook上,这会在5.3秒内完成1000万次(不是100万次)迭代。所以它很酷〜快100倍。当然好的一点取决于PRNG的速度。 Gnu的rand很快但不是那么随意。显然,它足以说明融合。该程序打印:

Avg for 11: 41.986926
Avg for 12: 35.997196

当我有更多时间时,会尝试Python impl。

答案 2 :(得分:0)

编译语言中程序的性能明显优于解释语言。这就是高频交易,视频游戏引擎和其他要求苛刻的软件以c ++等编译语言编程的原因。

在优化方面,您可以尝试使用pythons编译功能,或者本机运行程序而不是IDE内部。

答案 3 :(得分:0)

以下是您的代码段的Cython实现,它在我的计算机上以0.7秒的速度分析10 ^ 6个dicerolls:

from libc.stdlib cimport rand
import numpy as np
cimport numpy as np

DTYPE = np.int64
ctypedef np.int64_t DTYPE_T


cdef int simple_minded_randint(int min_val, int max_val):
    """For demonstration purpose only! Does not generate a uniform distribution."""
    return min_val + rand() % max_val


def diceroll(numrolls):
    cdef long NN = numrolls
    cdef long i
    cdef DTYPE_T ctr, prev, curr
    cdef int flag11, flag12
    cdef np.ndarray[DTYPE_T, ndim=1] t11 = np.zeros(NN, dtype=DTYPE)
    cdef np.ndarray[DTYPE_T, ndim=1] t12 = np.zeros(NN, dtype=DTYPE)

    for i in range(NN):
        prev = simple_minded_randint(1, 6)
        flag11 = 1
        flag12 = 1
        ctr = 2
        while flag11 or flag12:
            curr = simple_minded_randint(1, 6)
            if flag11 and prev == 1 and curr == 1:
                t11[i] = ctr
                flag11 = 0
            if flag12 and prev == 1 and curr == 2:
                t12[i] = ctr
                flag12 = 0
            ctr = ctr + 1
            prev = curr
    print('Mean t11:  %f' %(np.mean(t11)))
    print('Mean t12: %f' %(np.mean(t12)))

我添加了一些静态类型并使用C标准库中的随机生成器,因为在循环中使用np.random.randint()可以减慢相当多的速度。请注意,此随机生成器仅用于演示目的,因为它不会生成均匀分布,请参阅this answer