我正在模拟无限序列的掷骰子以计算平均时间"击中时间"一个序列。在这种特殊情况下,我正在寻找第一次出现的" 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的新手,并且已经使用了大约一个月。
我想知道是否有办法加速代码,可能是不同的算法,或者可能优化例程?它在编译语言和解释语言上的表现会有明显不同吗?我是
答案 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的投掷:
这很难实现,因为它一次可以占用多个状态,但是有一种称为子集构造的标准算法将其转换为确定性状态机,这是实施起来非常有效。在此处应用产生这个:
这是一个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。