我目前正在为模型出租车公司做一些班次调度模拟。该公司拥有350辆出租车,并且在任何一天都在使用。司机每人每班工作5班,每班12小时,每天有四班重叠。时间为3:00-15:00,15:00-3:00,16:00-4:00和4:00-16:00。我最初是用Python开发的,因为需要快速开发它,我认为性能是可以接受的。原始参数每天只需要两班(3:00-15:00和15:00-3:00),虽然性能不是很好,但对我的用途来说已经足够了。它可以在大约8分钟内为司机制定每周计划,使用简单的强力算法(评估所有潜在的掉期以查看情况是否可以改善。)
随着四个重叠的转变,表现绝对是糟糕的。每周安排一个小时需要一个多小时。我已经使用cProfile进行了一些分析,看起来主要的罪魁祸首是两种方法。一种方法是确定在轮班中放置驾驶员时是否存在冲突。它确保他们不是在同一天轮班,或在前一班或后班任职。每天只有两班,这很容易。人们只需确定驾驶员是否已经安排在班次之前或之后直接工作。随着四个重叠的转变,这变得更加复杂。第二个罪魁祸首是确定班次是白天还是夜班的方法。再次,使用原始的两个班次,这就像确定班次号码是偶数还是奇数一样容易,班次号码从0开始。第一班次(班次0)被指定为夜班,下一班是白天,等等等等。现在前两个是晚上,接下来的两个是,等等。这些方法互相称呼,所以我将他们的身体放在下面。
def conflict_exists(shift, driver, shift_data):
next_type = get_stype((shift+1) % 28)
my_type = get_stype(shift)
nudge = abs(next_type - my_type)
if driver in shift_data[shift-2-nudge] or driver in shift_data[shift-1-nudge] or driver in shift_data[(shift+1-(nudge*2)) % 28] or driver in shift_data[(shift+2-nudge) % 28] or driver in shift_data[(shift+3-nudge) % 28]:
return True
else:
return False
请注意,get_stype返回班次的类型,0表示夜班,1表示班次。
为了确定班次类型,我正在使用这种方法:
def get_stype(k):
if (k / 4.0) % 1.0 < 0.5:
return 0
else:
return 1
以下是cProfile的相关输出:
ncalls tottime percall cumtime percall
57662556 19.717 0.000 19.717 0.000 sim8.py:241(get_stype)
28065503 55.650 0.000 77.591 0.000 sim8.py:247(in_conflict)
有没有人对我如何改进此脚本的性能有任何狡猾的建议或提示?任何帮助将不胜感激!
干杯,
蒂姆编辑:对不起,我应该澄清,每个班次的数据都存储为一组,即shift_data [k]属于设置数据类型。
编辑2:
根据下面的请求添加主循环以及其他调用的方法。这有点乱,我为此道歉。
def optimize_schedule(shift_data, driver_shifts, recheck):
skip = set()
if len(recheck) == 0:
first_run = True
recheck = []
for i in xrange(28):
recheck.append(set())
else:
first_run = False
for i in xrange(28):
if (first_run):
targets = shift_data[i]
else:
targets = recheck[i]
for j in targets:
o_score = eval_score = opt_eval_at_coord(shift_data, driver_shifts, i, j)
my_type = get_stype(i)
s_type_fwd = get_stype((i+1) % 28)
if (my_type == s_type_fwd):
search_direction = (i + 2) % 28
end_direction = i
else:
search_direction = (i + 1) % 28
end_direction = (i - 1) % 28
while True:
if (end_direction == search_direction):
break
for k in shift_data[search_direction]:
coord = search_direction * 10000 + k
if coord in skip:
continue
if k in shift_data[i] or j in shift_data[search_direction]:
continue
if in_conflict(search_direction, j, shift_data) or in_conflict(i, k, shift_data):
continue
node_a_prev_score = o_score
node_b_prev_score = opt_eval_at_coord(shift_data, driver_shifts, search_direction, k)
if (node_a_prev_score == 1) and (node_b_prev_score == 1):
continue
a_type = my_type
b_type = get_stype(search_direction)
if (node_a_prev_score == 1):
if (driver_shifts[j]['type'] == 'any') and (a_type != b_type):
test_eval = 2
else:
continue
elif (node_b_prev_score == 1):
if (driver_shifts[k]['type'] == 'any') and (a_type != b_type):
test_eval = 2
else:
test_eval = 0
else:
if (a_type == b_type):
test_eval = 0
else:
test_eval = 2
print 'eval_score: %f' % test_eval
if (test_eval > eval_score):
cand_coords = [search_direction, k]
eval_score = test_eval
if (test_eval == 2.0):
break
else:
search_direction = (search_direction + 1) % 28
continue
break
if (eval_score > o_score):
print 'doing a swap: ',
print cand_coords,
shift_data[i].remove(j)
shift_data[i].add(cand_coords[1])
shift_data[cand_coords[0]].add(j)
shift_data[cand_coords[0]].remove(cand_coords[1])
if j in recheck[i]:
recheck[i].remove(j)
if cand_coords[1] in recheck[cand_coords[0]]:
recheck[cand_coords[0]].remove(cand_coords[1])
recheck[cand_coords[0]].add(j)
recheck[i].add(cand_coords[1])
else:
coord = i * 10000 + j
skip.add(coord)
if first_run:
shift_data = optimize_schedule(shift_data, driver_shifts, recheck)
return shift_data
def opt_eval_at_coord(shift_data, driver_shifts, i, j):
node = j
if in_conflict(i, node, shift_data):
return float('-inf')
else:
s_type = get_stype(i)
d_pref = driver_shifts[node]['type']
if (s_type == 0 and d_pref == 'night') or (s_type == 1 and d_pref == 'day') or (d_pref == 'any'):
return 1
else:
return 0
答案 0 :(得分:4)
没有什么能明显减缓这些功能,实际上它们并不慢。他们只是被召唤了很多。你说你正在使用强力算法 - 你能编写一个不尝试每种可能组合的算法吗?或者是否有更有效的方法,例如通过驱动程序而不是按班次存储数据?
当然,如果你需要即时加速,它可能会受益于运行像PyPy这样的解释器,或者使用Cython将关键部分转换为C语言。
答案 1 :(得分:3)
嗯。有趣和有趣的问题。我将不得不更多地看待它。现在,我有这个提供:你为什么要推出花车?我会按如下方式执行get_stype():
def get_stype(k):
if k % 4 < 2:
return 0
return 1
这不是一次大规模的加速,但它更快(更简单)。此外,无论何时喂get_stype
,您都不必执行mod 28,因为get_stype
中的mod 4已经处理了这个问题。
如果有重大改进,它们将以更好的算法形式出现。 (我不是说你的算法很糟糕,或者说有更好的算法。我没有花足够的时间去研究它。但是如果找不到更好的算法,那么进一步显着的速度增加必须来自使用PyPy,Cython,Shed Skin或完全用不同(更快)的语言重写。)
答案 2 :(得分:2)
我认为您的问题不是运行这两个功能所需的时间。请注意,函数的 percall 值为0.000
。这意味着每次调用函数时,都需要不到1毫秒。
我认为您的问题是调用函数的次数。 python中的函数调用很昂贵。例如,在我的机器上调用一个57,662,556次无效的函数需要7.15秒:
>>> from timeit import Timer
>>> t = Timer("t()", setup="def t(): pass")
>>> t.timeit(57662556)
7.159075975418091
我要好奇的是shift_data
变量。是值列表还是决定?
driver in shift_data[shift-2-nudge]
如果in
是一个列表,则O(N)
将花费O(1)
时间,如果它是一个词典,则会{{1}}时间。
编辑:由于shift_data值是设置,这应该没问题
答案 3 :(得分:2)
在我看来,在两个班次之间或两个夜班之间交换将永远不会有帮助。它不会改变司机喜欢班次的程度,也不会改变这些班次与其他班次的冲突。
所以我认为你应该能够最初计划两个班次,白天和黑夜,然后才将分配到班次的司机分成两个实际班次。