How To Reduce Json Marshalling Time

1,097 views
Skip to first unread message

pragya singh

unread,
Mar 8, 2024, 3:36:01 PM3/8/24
to golang-nuts
Hi,
"I am facing an issue in my Go code. I converted my read API from PHP to Go, but the response time of Go API is higher than PHP. We are returning the response in JSON format, and the response contains around 30k keys, which is taking too much time in JSON marshaling. We are adding all the response in map format as we can't use struct because response keys are not fixed and vary from user to user.

Robert Engels

unread,
Mar 8, 2024, 4:15:25 PM3/8/24
to pragya singh, golang-nuts
It is highly unlikely that the Go marshaling is the cause. I’m guessing you are probably not using a buffered output stream. 

PHP is written in C but depending on what you are doing it is either a script or calling out to another process (afaik) 

On Mar 8, 2024, at 2:35 PM, pragya singh <pragy...@gmail.com> wrote:

Hi,

"I am facing an issue in my Go code. I converted my read API from PHP to Go, but the response time of Go API is higher than PHP. We are returning the response in JSON format, and the response contains around 30k keys, which is taking too much time in JSON marshaling. We are adding all the response in map format as we can't use struct because response keys are not fixed and vary from user to user.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/a57a2a71-b0f9-41fb-93e9-bd54b57829b1n%40googlegroups.com.

Mike Schinkel

unread,
Mar 8, 2024, 7:53:05 PM3/8/24
to golang-nuts
Hi Pragya,

While Robert Engles is probably correct in identifying your bottleneck, if it does turn out to be slow JSON parsing here are a few things you can look at.

1. You mention you have to use a map because of response keys not being fixed. Be aware that you do not need to create a struct to match the full JSON. You can easily just create a flyweight struct which is a subset if for your use-case containing only the specific parts you need, for example.  

2. If you optionally need to parse portions of the JSON, but not always, you can use json.RawMessage to capture the properties you don't always need to parse, and then parse them only when you need to.

3. You can also declare an .UnmarshalJSON() method on a struct you are passing to json.Unmarshal(), or on any struct that is a property of your top level object, and then be fully in control of parsing the data, meaning you could combine with (a) json.RawMessage property(s) to part just the non-fixed keys as a map, and only for use-cases when you need to.

4. And finally, if you are willing to venture out from the Go standard library, there are numerous open-source packages that claim to be much faster than the standard library.  Usually you want to stick with the Go standard library so other Go developers will be familiar with it and to minimize dependencies that could potentially introduce a bug or security hole.  However, if you really need better performance it might be worth the added dependency.

While I cannot validate the performance claims of any of these packages, I can provide you with this list of packages that claim better JSON unmarshalling performance:
Benchmarks from authors of some of the packages (so take with a grain of salt):
And benchmarks from someone who is not an author of one of those packages:
Hope this helps.

-Mike

Robert Engels

unread,
Mar 8, 2024, 8:28:38 PM3/8/24
to Mike Schinkel, golang-nuts
Just to be clear for others - from a raw cpu performance perspective when looking at a typical application in whole - there is very little performance difference between compiled and even GC platforms- interpreted can lag behind a bit - but in almost all cases over the web it is IO performance/design that matters the most (most middleware connects to other middleware, etc). 

Some HFT/HPC systems deviate from this but by the description and reference to the previous implementation I doubt that is the case here. 

On Mar 8, 2024, at 6:53 PM, Mike Schinkel <mi...@newclarity.net> wrote:

Hi Pragya,

Mike Schinkel

unread,
Mar 8, 2024, 9:33:27 PM3/8/24
to golang-nuts
Hi Robert,

I am now confused.  While your original reply to this thread made perfect sense to me, your follow up for the purposes of clarification had the opposite effect, at least for me. I am not clear the point you are trying to make.

You mention the difference between compiled and interpreted and point out that typically there is not a large performance difference — where I assume you were referring to use of standard library functions in the interpreted language such as json_decode() in PHP. That is of course often true and I concur. (Of course, try writing a full JSON parser in pure PHP and we'll find a big difference in performance with a compiled language like Go.) 

But I am not seeing why that point is relevant in the thread because she was not asking "Why is PHP code slower than Go code?" in which case I would understand why you chose to make that distinction.

You also mention that IO performance (and design(?)) over the web is typically much more relevant performance-wise, on which I also concur, but given that both her PHP and her Go API endpoints presumably had much the same web-based latency and response time concerns, I'm struggling to understand why it was relevant to state it in this context.  Of course your point simply could have been not using a buffered output stream,  but you already mentioned, and your follow up did not make that connection clear.

So, can you please help me better understand the point you were trying to make?  As a follow up to my reply were you trying to imply that illustrating ways to optimize JSON parsing was something you felt I should not have posted in response to her question? That feels like in might have been your intent, but I could certainly have misinterpreted and if so would prefer to know rather than wrongly assume.

-Mike

Robert Engels

unread,
Mar 8, 2024, 9:56:38 PM3/8/24
to Mike Schinkel, golang-nuts
The point I was trying to make - maybe unclear - is that it is doubtful any raw performance differences between PHP and Go in terms of json encoding/decoding would be noticeable- unless it was a hft/hpc system which would be doubtful given that the original system was PHP. 

So if there is detectable difference in performance it is almost certainly related to IO management concerns (buffering, window sizes, etc)

Without detailed measurements along with how they were measured it’s hard to know with any certainty- so this was a Large guess to help you know where to look. 

On Mar 8, 2024, at 8:33 PM, Mike Schinkel <mi...@newclarity.net> wrote:

Hi Robert,

Robert Engels

unread,
Mar 8, 2024, 9:58:26 PM3/8/24
to Mike Schinkel, golang-nuts
Related, 30k of anything is largely meaningless for modern hardware in terms of cpu processing - where billions of operations a second is the norm. 

On Mar 8, 2024, at 8:55 PM, Robert Engels <ren...@ix.netcom.com> wrote:



Mike Schinkel

unread,
Mar 8, 2024, 11:25:06 PM3/8/24
to golang-nuts
Hi Robert,

Thanks for the reply.

I decided to run a quick benchmark so I compared Go 1.22's json.UnMarshal(any) using an any type with PHP 8.3.2's json_decode()
and found that PHP's option is 85% faster for a ~25Mb JSON file.  Optimizing it with a struct I was able to get Go down to being only about 25% slower than PHP's  json_decode().

Note that they are very rough benchmarks, probably only valid in gauging magnitude, not specific performance.

I wrote up the results and provided the code in this gist for anyone who might be interested.  For me, the takeaway is that if you really need fast JSON unmarshalling you probably need to look to one of the 3rd party JSON parser packages.

Whether or not the extra time it takes Go to parse JSON vs. PHP is actually relevant for her use-case remains to be seen given the potential order-of-magnitude difference in web request latency.  Still, given that she seems to be controlling for that web request latency by comparing her PHP API to her Go API would indicate — sans issues like buffering or slow Go middleware — that the performance of JSON parsing made indeed by one of the reasons she is seeing a difference.

Pragya, it would be really nice if you could follow up to close the loop and let us know what the actual bottleneck was and how you ended up solving it.

-Mike

Robert Engels

unread,
Mar 9, 2024, 12:16:51 AM3/9/24
to Mike Schinkel, golang-nuts
My guess is that most applications are decoding 25gb json files with any regularity. Even transferring 25 GB over the fastest of networks takes 20 secs? So that reduces the cost to less than 10%???

The test isn’t doing anything with the decoded json - maybe Go is 1000% faster in that. 

This is either a troll post or by someone that needs more education in performance monitoring. Sorry. 

Mike Schinkel

unread,
Mar 9, 2024, 12:31:32 AM3/9/24
to golang-nuts
On Saturday, March 9, 2024 at 12:16:51 AM UTC-5 Robert Engels wrote:
My guess is that most applications are decoding 25gb json files with any regularity. Even transferring 25 GB over the fastest of networks takes 20 secs? So that reduces the cost to less than 10%???

How about rather than guessing, you let the OP consider the input you gave in your first reply — which I acknowledged was likely the issue in my first reply — and then move on?

This is either a troll post or by someone that needs more education in performance monitoring. Sorry. 

Everything beyond your first reply on this thread — except when you replied to my ask for clarification — was unnecessary as you had already made your only point.

If there was trolling here, your reply to my first reply on the thread was the start of that trolling.

-Mike

P.S. The types of replies you've made on this thread is why I always ask myself if it worth the pain to answer someone's questions. Rest assured I will think twice next time before going to the effort.

Brian Candler

unread,
Mar 9, 2024, 3:43:49 AM3/9/24
to golang-nuts
Perhaps it would be helpful to give some concrete figures for the real-world application.

"the response time of Go API is higher than PHP" - how much? Are we talking, say, 1.2ms versus 1ms? Or 10ms versus 1ms? Is this testing between a client and server adjacent on the same LAN, or is the server running is a cloud somewhere?

"the response contains around 30k keys" - what's the total size of the response? Are the values for each key all simple strings?

Then, it would be useful to have small PHP and Go programs which reproduce the issue as seen, with data of this size.  You should prepare static data structures ready to be encoded for the response, so that you're only measuring the encoding and response time.

Then you could try pre-encoding the entire response to a JSON string, and return that string as the response, to eliminate the JSON encoding overhead.

Finally, it would be worth looking at the response using tcpdump or wireshark just to see if anything odd is happening, e.g. there is chunked encoding with lots of tiny chunks.

Robert Engels

unread,
Mar 9, 2024, 10:16:12 AM3/9/24
to Brian Candler, golang-nuts
Agreed. When I went to the github site the OP linked it seemed to me the original question was not genuine and was rather asked to generate traffic to the site. Reading the content was more evidence the question was not genuine (states “I am doing unscientific testing for orders of magnitude” then makes a big deal of a 14 second difference on 100 iterations) and was merely a article on a flawed micro benchmark. Lots of bold letters on the differences, etc. 

So yea, I was annoyed that I wasted my time posting trying to help on this. 

Pretty certain if the OP made only minimal changes to the test the difference would swing the other way. Did the op even try the “object” option in PHP to make the comparison similar?

On Mar 9, 2024, at 2:44 AM, 'Brian Candler' via golang-nuts <golan...@googlegroups.com> wrote:

Perhaps it would be helpful to give some concrete figures for the real-world application.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Mike Schinkel

unread,
Mar 9, 2024, 1:51:15 PM3/9/24
to golang-nuts
On Saturday, March 9, 2024 at 10:16:12 AM UTC-5 Robert Engels wrote:
When I went to the github site the OP linked it seemed to me the original question was not genuine and ...

Since the OP of this thread did not link anything, I assume you by "OP" you are referring to my comments on the thread?  

And from the fact that the original questioner is a different person than myself, how is it that you can conclude that her question was "not genuine?"  Do you have some kind of supernatural powers to discern her question? Or are you somehow implying sock-puppetry on my part here?!? 

Unbelievable.  
 
was rather asked to generate traffic to the site.

Are you f-ing serious?!?  WHAT site are you saying the "OP" (me?) was trying to drive traffic to?!?

My original post on the thread included links to many sites, NONE of which I own, have control of, or gain any benefit from.  WHY would I be trying to drive traffic to those sites?

Or are you referring to my gist in follow up?  So I initially made the effort to research JSON unmarshalling and then write an in-depth answer — which started by saying "Robert is probably right, but if it is JSON then consider this" — none of which you had any critique for on its merits. Instead you passive-aggressively undermined my reply with your immediate "just to be clear for others" reply to my email that happened to ignore that I said your initial reply was probably the concern. And why? To assert dominance on this list?

Do you think I want to generate traffic to my gist?!?  That I would go through all the effort to write that detailed reply, expect someone would be a jerk, then plan to post a link to my gist to generate traffic? For what benefit would I get for sending traffic to a gist?!?  I use gists to clearly indicate to others that I get would zero benefit from traffic to the link. 

Make your accusations explicit if you are going to accuse.
 
Reading the content was more evidence the question was not genuine

Again, the person asking the question was different than me (or are you accusing otherwise?)  So how the heck would my reply indicate that the original questioner's question was not genuine?  (Maybe it is not, I have no idea, but I assumed on face value that it was genuine. Would be a pretty sad world if we all assumed nefarious motives when there is zero evidence to indicate otherwise.) What benefit would they get from asking it if it were not genuine? What specifically about their question indicates to you it was not genuine?

And what benefit would I get from answering her, other than to feel good about helping someone? (Which, unfortunately, your replies have completely overwhelmed that tiny benefit.)
 
states “I am doing unscientific testing for orders of magnitude”
 
Wat?!?  

I wanted to do a sanity check to make sure that PHP json_decode() was in-fact faster than Go json.UnMarshal() before commenting further, but as I have other things to do in my life so I did not want to devote days to an exhaustive, scientific benchmark, so I did some basic testing to get an understanding of if in fact the reason the OP was seeing a performance difference could be Go vs. PHP.  

And since I did not want you or someone else to say "why didn't you show your work?" I went ahead and put in the extra effort to publish my code and results. I wrote “I am doing unscientific testing for orders of magnitude” in a pro-active attempt to keep some jerk from having a "Duty Calls" moment and take me to task for by claiming "my benchmark was not scientific."  But clearly my proactive attempt to ward of specious criticism did not stop you from being that jerk. No good deed goes unpunished, it seems.

makes a big deal of a 14 second difference on 100 iterations

So presenting results is "making a big deal?!?"  Good to know, I guess.

And what exact results would rise to meet your bar for a "big deal?"
 
and was merely a article on a flawed micro benchmark.

If the benchmark was so flawed, why don't you fix it then. I published all the work I did.

Rather than taking easy pot-shots like a jerk, why not do some work and present alternate numbers?  

Or better, just not criticize in a passive-aggressive manner anything you think is not up to your standards?
 
Lots of bold letters on the differences, etc. 

So now we are determining that if someone uses formatting to make their posts easier to scan — an approach to writing technical documents I have used for decades, as evidence from my former life — that that somehow indicates they have nefarious intent?!?  Wow, you must really be fun at parties.
 
So yea, I was annoyed that I wasted my time posting trying to help on this. 

You were annoyed that you spent 3 minutes to shoot off an initial response that required no time to research or prepare?

*I* was super annoyed that I spent several hours writing up an answer that explained how to deal with slow JSON unmarshalling in Go while indicating that the OP should look first at the concerns already mentioned only to see an immediately reply to my reply undermining my answer in a passive-aggressive manner from the very same person who I wrote "was probably right" about the performance issue.  I thought "What a jerk, but I will ask to clarify because maybe I could just be misunderstanding."  Well, clearly I was not misunderstanding.

Did the op even try the “object” option in PHP to make the comparison similar? Pretty certain if the OP made only minimal changes to the test the difference would swing the other way. 

And there you go with the assumptions again.  You do not understand how PHP works, do you?  Objects and arrays are not that much different inside PHP.

Did you even try to make those minimal changes and test for yourself?  I included everything I used to test in that gist.  I used ChatGPT to generate the benchmark which I used pretty much exactly as it provided. And yes it is flawed for specific performance, I am sure, but does show that PHP's default JSON decoding is faster than Go's default JSON unmarshalling.

But to address your criticism I updated the gist to include to PHP generating objects. It required more memory and was 7.5% slower, but still 20% faster than Go.  

That said, comparing apples-to-apples is not super relevant here because I assume the OP just wants unmarshaling JSON in Go to be faster and does not care about optimizing PHP. Again, the sole reason I did the "benchmark" was as a sanity check to verify if PHP was indeed faster than Go or not.
 
Pretty certain if the OP made only minimal changes to the test the difference would swing the other way. 

Since you are "pretty certain," why don't you quit with the assumptions and actually do your own benchmark showing your claims? But I do realize it is much easier to just take pot-shots, though.

I do get that you think her problem is probably not the JSON parsing — and I acknowledged up front that was probably the correct concern — but you do not know for sure that the JSON parsing was not her issue — and as there are at least nine (9) 3rd party packages designed to perform better than json.UnMarshal() it was not unreasonable to think that maybe, just maybe the performance of JSON parsing is an issue for her., and for me to post an answer that addressed that, just in case. 

In summary, I have found your hostility on this thread to be completely inappropriate. I simply answered a question about working with JSON in Go — after I acknowledged your initial reply was likely the cause of the OP's concern — in an attempt to help the OP and any others who might need faster JSON parsing.  Then — for some reason only you can know — you decided to be passive-aggressive in reply to my answer, and then explicitly attack me and my participation on this list when I posted a follow up showing the sanity checking I had done. I guess in your world, the only replies to this list that are reasonable are the ones you think are reasonable, and all others deserve a condescending response 

-Mike

Ian Lance Taylor

unread,
Mar 9, 2024, 2:16:48 PM3/9/24
to golang-nuts
This thread has gotten heated.  I think it's time to move on to different topics and give this discussion a rest.  Thanks.

Ian

Robert Engels

unread,
Mar 10, 2024, 5:54:29 AM3/10/24
to Ian Lance Taylor, golang-nuts
Apologies.

On Mar 9, 2024, at 1:16 PM, Ian Lance Taylor <ia...@golang.org> wrote:


This thread has gotten heated.  I think it's time to move on to different topics and give this discussion a rest.  Thanks.

Ian

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages