Python(Django)中基于Django查询集创建JSON的最快方法是什么。请注意,在模板中按建议here解析它不是一种选择。
背景是我创建了一个循环遍历树中所有节点的方法,但在转换大约300个节点时已经非常慢了。我想到的第一个(也可能是最糟糕的)想法是以某种方式“手动”创建json。请参阅下面的代码。
#! Solution 1 !!#
def quoteStr(input):
return "\"" + smart_str(smart_unicode(input)) + "\""
def createJSONTreeDump(user, node, root=False, lastChild=False):
q = "\""
#open tag for object
json = str("\n" + indent + "{" +
quoteStr("name") + ": " + quoteStr(node.name) + ",\n" +
quoteStr("id") + ": " + quoteStr(node.pk) + ",\n" +
)
childrenTag = "children"
children = node.get_children()
if children.count() > 0 :
#create children array opening tag
json += str(indent + quoteStr(childrenTag) + ": [")
#for child in children:
for idx, child in enumerate(children):
if (idx + 1) == children.count():
//recursive call
json += createJSONTreeDump(user, child, False, True, layout)
else:
//recursive call
json += createJSONTreeDump(user, child, False, False, layout)
#add children closing tag
json += "]\n"
#closing tag for object
if lastChild == False:
#more children following, add ","
json += indent + "},\n"
else:
#last child, do not add ","
json += indent + "}\n"
return json
要呈现的树结构是使用mptt构建的树,其中调用.get_children()返回节点的所有子项。
模型看起来就像这样,只需要处理其他所有事情。
class Node(MPTTModel, ExtraManager):
"""
Representation of a single node
"""
name = models.CharField(max_length=200)
parent = TreeForeignKey('self', null=True, blank=True, related_name='%(app_label)s_%(class)s_children')
模板var root = {{ jsonTree|safe }}
编辑: 基于this回答,我创建了以下代码(绝对是更好的代码),但感觉只是稍快一些。
解决方案2:
def serializable_object(node):
"Recurse into tree to build a serializable object"
obj = {'name': node.name, 'id': node.pk, 'children': []}
for child in node.get_children():
obj['children'].append(serializable_object(child))
return obj
import json
jsonTree = json.dumps(serializable_object(nodeInstance))
解决方案3:
def serializable_object_List_Comprehension(node):
"Recurse into tree to build a serializable object"
obj = {
'name': node.name,
'id': node.pk,
'children': [serializable_object(ch) for ch in node.get_children()]
}
return obj
解决方案4:
def recursive_node_to_dict(node):
result = {
'name': node.name, 'id': node.pk
}
children = [recursive_node_to_dict(c) for c in node.get_children()],
if children is not None:
result['children'] = children
return result
from mptt.templatetags.mptt_tags import cache_tree_children
root_nodes = cache_tree_children(root.get_descendants())
dicts = []
for n in root_nodes:
dicts.append(recursive_node_to_dict(root_nodes[0]))
jsonTree = json.dumps(dicts, indent=4)
解决方案5(使用select_related到pre_fetch,但不确定是否正确使用)
def serializable_object_select_related(node):
"Recurse into tree to build a serializable object, make use of select_related"
obj = {'name': node.get_wbs_code(), 'wbsCode': node.get_wbs_code(), 'id': node.pk, 'level': node.level, 'position': node.position, 'children': []}
for child in node.get_children().select_related():
obj['children'].append(serializable_object(child))
return obj
解决方案6(改进的解决方案4,使用子节点的缓存):
def recursive_node_to_dict(node):
result = {
'name': node.name, ''id': node.pk,
#notice the use of node._cached_children instead of node.get_children()
'children' : [recursive_node_to_dict(c) for c in node._cached_children]
}
通过:
调用from mptt.templatetags.mptt_tags import cache_tree_children
subTrees = cache_tree_children(root.get_descendants(include_self=True))
subTreeDicts = []
for subTree in subTrees:
subTree = recursive_node_to_dict(subTree)
subTreeDicts.append(subTree)
jsonTree = json.dumps(subTreeDicts, indent=4)
#optional clean up, remove the [ ] at the beginning and the end, its needed for D3.js
jsonTree = jsonTree[1:len(jsonTree)]
jsonTree = jsonTree[:len(jsonTree)-1]
下面你可以看到按照MuMind的建议使用cProfile创建的分析结果,设置一个Django视图来启动独立方法profileJSON(),后者又调用不同的解决方案来创建JSON输出。
def startProfileJSON(request):
print "startProfileJSON"
import cProfile
cProfile.runctx('profileJSON()', globals=globals(), locals=locals())
print "endProfileJSON"
结果:
解决方案1: 3350347函数调用(3130372个原始调用),时间为4.969秒(details)
解决方案2: 2533705函数调用(2354516原始调用)在3.630秒(details)
解决方案3: 2533621函数调用(2354441个原始调用)在3.684秒(details)
解决方案4: 2812725函数调用(2466028个原始调用),在3.840秒(details)
解决方案5: 2536504函数调用(2357256原始调用)3.779秒(details)
解决方案6(改进的解决方案4): 2593122函数调用(2299165个原始调用)在3.663秒(details)
讨论:
解决方案1:自己的编码实现。坏主意
解决方案2 + 3:目前最快,但仍然非常缓慢
解决方案4:看起来很有希望缓存孩子,但确实表现相似,目前产生无效的json,因为孩子被放入double []:
"children": [[]] instead of "children": []
解决方案5:使用select_related没有区别,但可能以错误的方式使用,因为一个节点总是有一个ForeignKey到它的父节点,我们正在从root解析为子节点。
更新:解决方案6:对我来说,使用缓存子节点看起来是最干净的解决方案。但是只执行类似于解决方案2 + 3.这对我来说很奇怪。
还有更多关于性能改进的想法吗?
答案 0 :(得分:22)
我怀疑到目前为止最大的减速是每个节点将进行1次数据库查询。与数百次往返数据库的往返相比,json渲染是微不足道的。
您应该在每个节点上缓存子节点,以便可以一次完成这些查询。 django-mptt有一个cache_tree_children()函数,你可以用它来完成。
import json
from mptt.templatetags.mptt_tags import cache_tree_children
def recursive_node_to_dict(node):
result = {
'id': node.pk,
'name': node.name,
}
children = [recursive_node_to_dict(c) for c in node.get_children()]
if children:
result['children'] = children
return result
root_nodes = cache_tree_children(Node.objects.all())
dicts = []
for n in root_nodes:
dicts.append(recursive_node_to_dict(n))
print json.dumps(dicts, indent=4)
自定义json编码,虽然它可能在某些情况下提供轻微的加速,但我强烈反对,因为它将是很多代码,并且它很容易获得very wrong。
答案 1 :(得分:8)
您的更新版本看起来会有很少的开销。我认为使用列表理解会更有效(也更具可读性):
def serializable_object(node):
"Recurse into tree to build a serializable object"
obj = {
'name': node.name,
'children': [serializable_object(ch) for ch in node.get_children()]
}
return obj
除此之外,您所能做的就是对其进行分析以找出瓶颈。编写一些独立代码,加载和序列化您的300个节点,然后使用
运行它python -m profile serialize_benchmark.py
(或-m cProfile
如果效果更好的话。)
可以看到3个不同的潜在瓶颈:
.get_children()
和.name
) - 我不确定究竟发生了什么,但我有这样的代码,为每个节点添加数据库查询巨大的开销。如果这是您的问题,您可以将其配置为使用select_related或类似的东西进行“急切加载”。serializable_object
本身) - 只需确保serializable_object
的ncalls看起来像一个合理的数字。如果我理解你的描述,它应该在300附近。json.dumps(nodeInstance)
) - 因为你说它只有300个节点,所以不是一个可能的罪魁祸首,但如果你确实看到这会占用大量的执行时间,请确保你已经编译了加速JSON正常工作。如果你无法分析它,请制作一个精简版本,例如,递归调用node.name
和node.get_children()
,但不会将结果存储在数据结构中,并且看看如何比较。
更新:
在解决方案3中有2192个调用execute_sql
,在解决方案5中调用2192,所以我认为过多的数据库查询是一个问题,并且select_related
没有像上面那样使用它。看django-mptt issue #88: Allow select_related in model methods表明您使用它或多或少是正确的,但我有疑问,而get_children
与get_descendants
可能会产生巨大的差异。
copy.deepcopy
还有很多时间,这令人费解,因为你没有直接调用它,我也没有看到它从MPTT代码调用。什么是tree.py?
如果您正在进行大量分析工作,我强烈推荐使用非常灵活的工具RunSnakeRun,它可以让您以非常方便的网格形式查看您的个人资料数据,并更好地理解数据快。
无论如何,这是另一种简化数据库方面的尝试:
import weakref
obj_cache = weakref.WeakValueDictionary()
def serializable_object(node):
root_obj = {'name': node.get_wbs_code(), 'wbsCode': node.get_wbs_code(),
'id': node.pk, 'level': node.level, 'position': node.position,
'children': []}
obj_cache[node.pk] = root_obj
# don't know if the following .select_related() does anything...
for descendant in node.get_descendants().select_related():
# get_descendants supposedly traverses in "tree order", which I think
# means the parent obj will always be created already
parent_obj = obj_cache[descendant.parent.pk] # hope parent is cached
descendant_obj = {'name': descendant.get_wbs_code(),
'wbsCode': descendant.get_wbs_code(), 'id': descendant.pk,
'level': descendant.level, 'position': descendant.position,
'children': []}
parent_obj['children'].append(descendant_obj)
obj_cache[descendant.pk] = descendant_obj
return root_obj
请注意,这不再是递归的。它通过节点迭代地进行,理论上是在他们的父母被访问之后,并且它都使用了对MPTTModel.get_descendants()
的一个大调用,所以希望这是优化得很好并且缓存.parent
等等(或者可能还有更多直接获取父键的方法?)。它最初会创建没有孩子的每个obj,然后将所有值“移植”给他们的父母。
答案 2 :(得分:1)
将数据整理到嵌套的词典或列表中,然后调用json转储方法:
import json
data = ['foo', {'bar': ('baz', None, 1.0, 2)}]
json.dump(data)
答案 3 :(得分:0)
在玩了一下之后,我发现解决方案都太慢了,因为mptt本身正在多次扫描缓存到get_children
。
利用mptt以正确的顺序返回行以轻松构建树的事实,我做到了这一点:
def flat_tree_to_dict(nodes, max_depth):
tree = []
last_levels = [None] * max_depth
for n in nodes:
d = {'name': n.name}
if n.level == 0:
tree.append(d)
else:
parent_dict = last_levels[n.level - 1]
if 'children' not in parent_dict:
parent_dict['children'] = []
parent_dict['children'].append(d)
last_levels[n.level] = d
return tree
对于我的数据集,这比其他解决方案快10倍,因为它是O(n),只迭代数据一次。
我这样用:
json.dumps(flat_tree_to_dict(Model.objects.all(), 4), indent=4)