为什么Flask的jsonify方法很慢?

18

我正在使用 Flask 编写一个返回 JSON 的 API。每个 Flask 函数的形式如下:

from flask import jsonify
@app.route('/getdata')
def get_data():
    data = load_data_as_dict()
    return jsonify(data)

如果我返回大量数据,调用这个函数需要大约1.7秒的时间。但是,如果我这样做:

from flask import Response
@app.route('/getdata')
def get_data():
    data = load_data_as_dict()
    data_as_str = json.dumps(data)
    return Response(response=data_as_str, status=200, mimetype="application/json"

这个函数大约在0.05秒内完成。

有人能告诉我为什么jsonify如此缓慢吗?返回原始的Flask响应是否有问题?

3个回答

14

我猜测它与缩进以及进行漂亮的json输出有很大关系。这是方法定义(我删除了注释以节省空间,完整代码可在 这里找到):

def jsonify(*args, **kwargs):
    indent = None
    separators = (',', ':')

    if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_xhr:
        indent = 2
        separators = (', ', ': ')

    if args and kwargs:
        raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
    elif len(args) == 1:  # single args are passed directly to dumps()
        data = args[0]
    else:
        data = args or kwargs

    return current_app.response_class(
        (dumps(data, indent=indent, separators=separators), '\n'),
        mimetype=current_app.config['JSONIFY_MIMETYPE']
    )

dumps 函数在模块 simplejson.dumps 存在时使用该模块,否则使用 json.dumps 函数。


没错,我试过了。如果你重新定义 jsonify 并删除缩进,它会运行得更快。在我的测试中,时间从17秒变成了0.1秒,快了170倍! - Rugnar

7

jsonify()只是包装了json.dumps()。然而,根据您的Flask应用程序的配置和您使用的Flask版本,它可能会将indent=2separators=(', ', ': ')传递给json.dumps。(如果您不熟悉这些参数,请参阅漂亮打印文档https://docs.python.org/3/library/json.html)。

传递这些参数会大大减慢json.dumps的速度。以181MB的citylots.json文件为样本数据,来自https://github.com/zemirco/sf-city-lots-json,这些漂亮打印参数将json.dumps()的运行时间从7秒增加到31秒,在我的MacBook Pro上:

>>> import time 
>>> import json
>>> citylots = json.load(open('citylots.json'))
>>> start = time.time(); x = json.dumps(citylots); print(time.time() - start)
7.165302753448486
>>> x = None
>>> start = time.time(); x = json.dumps(citylots, indent=2, separators=(', ', ': ')); print(time.time() - start)
31.19125771522522

从Flask 1.0开始,如果以下任一条件成立,则会发生这种昂贵的漂亮打印:

  • 您在应用程序配置中明确设置了JSONIFY_PRETTYPRINT_REGULARTrue(默认情况下为False),或者
  • 您正在以调试模式运行应用程序。

(您可以在Flask 1.0.2代码中查看这些条件https://github.com/pallets/flask/blob/1.0.2/flask/json/__init__.py#L309。)

如果您使用的是Flask >=1.0,并且有(可能不寻常的)需要在调试模式下禁用漂亮的打印,您始终可以通过复制和粘贴内置的jsonify定义并删除所有漂亮的打印逻辑来实现自己的jsonify

from flask import current_app
from json import dumps

def jsonify(*args, **kwargs):
    if args and kwargs:
        raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
    elif len(args) == 1:  # single args are passed directly to dumps()
        data = args[0]
    else:
        data = args or kwargs

    return current_app.response_class(
        dumps(data) + '\n',
        mimetype=current_app.config['JSONIFY_MIMETYPE']
    )

如果您使用的是 Flask 1.0 以前的版本,则只有在以下两种情况下才会进行漂亮打印:
  • 您没有在应用程序的配置中明确设置 JSONIFY_PRETTYPRINT_REGULAR 为 False(默认为 True),并且
  • 当前请求不是 XHR 请求。
在这些旧版本中,永远不需要重新定义 jsonify 来消除漂亮打印,因为您可以这样做:
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False

如果你正在使用 Flask 的 1.0 版本之前的版本,并且只想在生产环境中禁用漂亮的打印输出,那么无需更改代码;相反,只需升级到最新版本的 Flask。


6

我花了一些时间才明白,但是 Flask 的 jsonify 函数设置了编码器的 sort_keys 参数,默认值似乎为 True

补充:

JSON_SORT_KEYS = False

通过配置,我获得了7倍的速度提升,适用于较大的JSON结构。


哦,哇,这是一个相当重要的事情,我在我的回答中完全忽略了它!我可能会相应地更新我的回答。 - Mark Amery
2
请小心处理此问题,因为它可能会导致大量不必要的缓存未命中,原因是相同数据的顺序不同。 - booshong
在调试模式下,同样的大对象需要花费2.5秒来序列化(因为需要漂亮打印),而在非调试模式下仅需50毫秒。 - FrankyBoy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接