Skip to content

json.dump much slower than dumps #56343

Closed

Description

@poq
mannequin
BPO 12134
Nosy @rhettinger, @terryjreedy, @pitrou, @ezio-melotti, @merwok

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = 'https://github.com/rhettinger'
closed_at = <Date 2011-06-26.14:41:50.827>
created_at = <Date 2011-05-21.13:38:06.321>
labels = ['easy', 'docs', 'library', 'performance']
title = 'json.dump much slower than dumps'
updated_at = <Date 2011-06-26.14:41:50.825>
user = 'https://bugs.python.org/poq'

bugs.python.org fields:

activity = <Date 2011-06-26.14:41:50.825>
actor = 'rhettinger'
assignee = 'rhettinger'
closed = True
closed_date = <Date 2011-06-26.14:41:50.827>
closer = 'rhettinger'
components = ['Documentation', 'Library (Lib)']
creation = <Date 2011-05-21.13:38:06.321>
creator = 'poq'
dependencies = []
files = []
hgrepos = []
issue_num = 12134
keywords = ['easy']
message_count = 12.0
messages = ['136439', '136440', '136641', '136691', '137146', '137166', '137169', '137170', '137691', '137704', '137707', '139182']
nosy_count = 7.0
nosy_names = ['rhettinger', 'terry.reedy', 'pitrou', 'ezio.melotti', 'eric.araujo', 'docs@python', 'poq']
pr_nums = []
priority = 'normal'
resolution = 'rejected'
stage = 'needs patch'
status = 'closed'
superseder = None
type = 'performance'
url = 'https://bugs.python.org/issue12134'
versions = ['Python 2.7', 'Python 3.2', 'Python 3.3']

Activity

  1. poq commented on May 21, 2011

    poqmannequin
    MannequinAuthor
    import json, timeit
    obj = [[1,2,3]*10]*10
    class writable(object):
    	def write(self, buf): pass
    w = writable()
    print('dumps: %.3f' % timeit.timeit(lambda: json.dumps(obj), number=10000))
    print('dump:  %.3f' % timeit.timeit(lambda: json.dump(obj,w), number=10000))

    On my machine this outputs:
    dumps: 0.391
    dump: 4.501

    I believe this is mostly caused by dump using JSONEncoder.iterencode without _one_shot=True, resulting in c_make_encoder not being used.

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    and removed on May 21, 2011
  3. pitrou commented on May 21, 2011

    @pitrou
    Member

    This is indeed the case. And the solution is obvious: call dumps() and then write() the result yourself. If dump() used _one_shot=True, it would defeat the purpose of minimizing memory consumption by not buffering the whole result.

  4. merwok commented on May 23, 2011

    @merwok
    Member

    I believe Antoine is saying “works as intended”, i.e. not a bug. I’m not sure it’s worth a doc change.

  5. poq commented on May 23, 2011

    poqmannequin
    MannequinAuthor

    Alright.

    I wouldn't mind a little note in the docs; I certainly did not expect that these two functions would perform so differently.

    Would it be very difficult though to add buffering support the C encoder?

  6. terryjreedy commented on May 28, 2011

    @terryjreedy
    Member

    From just reading the docs, it appears that json.dump(obj,fp) == fp.write(json.dumps(obj)) and it is easy to wonder why .dump even exists, as it seems a trivial abbreviation (and why not .dump and .dumpf instead). Since, '_one_shot' and 'c_make_encoder' are not mentioned in the doc, there is no hint from these either. So I think a doc addition is needed.

    The benchmark is not completely fair as the .dumps timing omits the write call. For the benchmark, that would be trivial. But in real use on multitasking systems with slow (compared to cpu speed) output channels, the write time might dominate. I can even imagine .dump sometimes winning by getting chunks into a socket buffer and possibly out on the wire, almost immediately, instead of waiting to compute the entire output, possibly interrupted by task swaps. So I presume *this* is at least part of the reason for the incremental .dump.

    I changed 'pass' to 'print(bug)' in class writable and verified that .dump is *very* incremental. Even '[' and ']' are separate outputs.

    DOC suggestion: (limited to CPython since spec does not prohibit naive implementation of .dump given above) After current .dumps line, add

    "In CPython, json.dumps(o), by itself, is faster than json.dump(o,f), at the expense of using more space, because it creates the entire string at once, instead of incrementally writing each piece of o to f. However, f.write(json.dumps(o)) may not be faster."

  7. ezio-melotti commented on May 29, 2011

    @ezio-melotti
    Member

    The name dump and dumps exist to match the same API provided by pickle and marshal.

    I agree that a note marked as CPython implementation detail should be added.

  8. rhettinger commented on May 29, 2011

    @rhettinger
    Contributor

    Please don't add notes of this sort to the docs.

  9. ezio-melotti commented on May 29, 2011

    @ezio-melotti
    Member

    Are you against the proposed wording or the note itself?

    Stating that in CPython json.dump doesn't use the C accelerations is a reasonable thing to do imho.

  10. pitrou commented on Jun 5, 2011

    @pitrou
    Member

    "In CPython, json.dumps(o), by itself, is faster than json.dump(o,f),
    at the expense of using more space, because it creates the entire
    string at once, instead of incrementally writing each piece of o to f.
    However, f.write(json.dumps(o)) may not be faster."

    Uh, talking about "CPython" is not very helpful here and only muddies
    the waters IMO.
    Something like "typical implementations of dump() will try to write the
    result in small chunks and will therefore trade lower memory usage for
    higher serialization time. If you have enough memory and care about
    performance, consider using dumps() and write the result yourself with a
    single write() call".

  11. terryjreedy commented on Jun 5, 2011

    @terryjreedy
    Member

    With 'will try to ' and the next 'will ' omitted, I agree that Antoine's version is better than mine.

  12. poq commented on Jun 5, 2011

    poqmannequin
    MannequinAuthor

    dump() is not slower because it's incremental though. It's slower because it's pure Python. I don't think there is necessarily a memory/speed trade-off; it should be possible to write an incremental encoder in C as well.

  13. rhettinger commented on Jun 26, 2011

    @rhettinger
    Contributor

    As Antoine and Eric stated, the module is working as intended and we don't document implementation details and generally stay away from talking about performance in the docs.

  14. transferred this issue fromon Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

docsDocumentation in the Doc direasyperformancePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directory

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions