我试图通过使用query.fetch_async()异步运行多个子查询来减少AppEngine查询的执行时间。但是,与串行运行查询相比,增益似乎很小。
下面是一些说明问题的最小示例代码(在Python中) - 首先是异步运行的函数:
def run_parallel(self, repeats):
start = datetime.utcnow()
futures = []
for i in xrange(0, repeats):
q = User.query()
f = q.fetch_async(300, keys_only=True)
futures.append(f)
while futures:
f = ndb.Future.wait_any(futures)
futures.remove(f)
results = f.get_result()
delta_secs = (datetime.utcnow() - start).total_seconds()
self.response.out.write("Got %d results, delta_sec: %f<br>\n" %(len(results), delta_secs))
然后是相应串行运行的函数:
def run_serial(self, repeats):
start = datetime.utcnow()
for i in xrange(0, repeats):
q = User.query()
results = q.fetch(300, keys_only=True)
delta_secs = (datetime.utcnow() - start).total_seconds()
self.response.out.write("Got %d results, delta_sec: %f<br>\n" %(len(results), delta_secs))
运行这两个函数的输出各10次(不在开发服务器上),即以下调用:
run_parallel(10)
run_serial(10)
如下:
运行并行查询...
得到300条结果,delta_sec:0.401090
得到300个结果,delta_sec:0.501700
得到300条结果,delta_sec:0.596110
得到300条结果,delta_sec:0.686120
得到300条结果,delta_sec:0.709220
得到300条结果,delta_sec:0.792070
得到300个结果,delta_sec:0.816500
得到300结果,delta_sec:0.904360
得到300个结果,delta_sec:0.993600
得到300条结果,delta_sec:1.017320
运行串行查询...
得到300个结果,delta_sec:0.114950
得到300条结果,delta_sec:0.269010
得到300条结果,delta_sec:0.370590
得到300条结果,delta_sec:0.472090
得到300个结果,delta_sec:0.575130
得到300条结果,delta_sec:0.678900
得到300条结果,delta_sec:0.782540
得到300条结果,delta_sec:0.883960
得到300条结果,delta_sec:0.986370
得到300个结果,delta_sec:1.086500
因此,并行和串行版本大约需要大约相同的时间,大约1秒钟。 Appstat如下,前10个查询是并行查询,后10个是串行查询:
从这些统计数据看,10个第一个查询确实并行运行,但与单个串行查询相比,每个查询都花费了不成比例的时间。看起来他们可能会以某种方式阻止,等待彼此完成。
所以我的问题:我的代码运行异步查询有什么问题吗?或AppEngine上异步查询的效率是否存在固有限制?
我想知道这种行为是否可能是由以下原因引起的:
所以,我有点不知所措。任何建议都将不胜感激。
更新1
遵循Bruyere的建议我尝试使用db而不是ndb,并且我尝试交换并行和串行版本的顺序。结果是一样的。
更新2
这是一个涉及同一问题的相关帖子;仍然没有回答为什么并行查询如此低效:
Best practice to query large number of ndb entities from datastore
更新3
使用Java SDK的相应代码非常整齐地并行化。以下是Java appstats:
确切地说,这个Java实现是显式多线程的,在不同的线程中运行查询;这是必要的,因为与AppEngine文档声称的相反,使用查询迭代器而不是实际上导致查询并行执行。
我尝试在Python版本中使用带有同步查询调用的显式多线程,但结果与原始Python版本相同。
Java版本按预期执行的事实意味着糟糕的Python异步性能不是由AppEngine CPU瓶颈引起的。
我能想到的唯一替代解释是Python的全局解释器锁正在引发颠簸。这一点得到以下事实的支持:减少GIL检查间隔(使用sys.setcheckinterval)会加剧糟糕的异步性能。
这是令人惊讶的:鉴于查询是IO绑定的,GIL不会产生如此严重的影响。我推测也许RPC输入缓冲区足够小,以至于异步调用在检索结果时经常恢复,这可能会导致GIL颠簸。我已经查看了Python AppEngine库代码,但是低级RPC调用是由_apphosting_runtime ___ python__apiproxy.MakeCall()创建的,它似乎是封闭源代码。
唉,我的结论是Python AppEngine运行时不适合我需要的那种并行查询,除了转移到Java运行库之外别无其他选择。我真的想避免这种情况,所以我真的希望我错了,错过了一些明显的东西。任何建议或指示将不胜感激。
谢谢!
答案 0 :(得分:1)
你是否总是在run_serial之前运行run_parallel?如果是这样,ndb缓存结果并且能够更快地提取信息。尝试翻转结果,或者更好地尝试使用DB,因为ndb只是包含memcache结果的包装器。
答案 1 :(得分:0)
主要问题是您的示例主要是CPU绑定而不是IO绑定。特别是,大部分时间可能花在解码RPC结果上,这些结果由于GIL而无法在python中高效完成。 Appstats的一个问题是它测量从发送RPC到调用get_result()时的RPC时序。这意味着调用get_result之前花费的时间似乎来自RPC。
如果您发出IO绑定的RPC(即使数据存储更加困难的查询),您将开始看到并行查询的性能提升。