头图

foreword

The parsing library xjson , which was recently written before continuous optimization JSON is mainly optimized in two aspects.

The first is to support outputting a JSONObject object as a JSON string.

In the previous version, only the built-in Print function was used to print data:

 func TestJson4(t *testing.T)  {
    str := `{"people":{"name":{"first":"bob"}}}`
    first := xjson.Get(str, "people.name.first")
    assert.Equal(t, first.String(), "bob")
    get := xjson.Get(str, "people")
    fmt.Println(get.String())
    //assert.Equal(t, get.String(),`{"name":{"first":"bob"}}`)
}

Output:

 map[name:map[first:bob]]

<!--more-->

After this optimization, the JSON string can be output directly:

The implementation process is also very simple. You only need to recursively traverse the data in the object, and then splicing strings. The core code is as follows:

 func (r Result) String() string {
    switch r.Token {
    case String:
        return fmt.Sprint(r.object)
    case Bool:
        return fmt.Sprint(r.object)
    case Number:
        i, _ := strconv.Atoi(fmt.Sprint(r.object))
        return fmt.Sprintf("%d", i)
    case Float:
        i, _ := strconv.ParseFloat(fmt.Sprint(r.object), 64)
        return fmt.Sprintf("%f", i)
    case JSONObject:
        return object2JSONString(r.object)
    case ArrayObject:
        return object2JSONString(r.Array())
    default:
        return ""
    }
}

Optimizing with bitwise operations

The second optimization is mainly to improve performance. When querying a complex JSON data, the performance is improved by about ⏫16%.

 # 优化前
BenchmarkDecode-12         90013             66905 ns/op           42512 B/op       1446 allocs/op

# 优化后
BenchmarkDecode-12        104746             59766 ns/op           37749 B/op       1141 allocs/op

Here are some of the key changes:

During the JSON parsing process, there will be a finite state machine state migration process, and there may be multiple states during the migration.

For example, the currently parsed token value is { , then its next token may be ObjectKey:"name" , or BeginObject:{ , of course, it may be EndObject:} ,
So before optimization, I stored all the states in a set. During the parsing process, if the state does not meet the expected list, a syntax exception will be thrown.

So before optimization, we traverse this set to make judgments. This time complexity is O(N) , but when we change to bit operation, it is different, and the time complexity directly becomes O(1) , while saving a slice of storage space.

Let's simply analyze why this bit operation achieves the same effect of judging whether a data is in a set.

First, take these two states as an example:

 StatusObjectKey   status = 0x0002
    StatusColon       status = 0x0004

Their corresponding binary data are:

 StatusObjectKey   status = 0x0002 //0010
    StatusColon       status = 0x0004 //0100

When we calculate these two data | , the obtained data is 0110 :

 A:0010
B:0100

C:0110

At this time, if we use these two original data and C:0110 to do & operation, it will be restored to the two data just now.

 // input:
A:0010
C:0110

// output:
A:0010

----------
// input:
B:0100
C:0110

// output:
B:0100

But when we change D and C to find & :

 D: 1000 // 0x0008 对应的二进制为 1000
C: 0110
D':0000

A 0 value will be obtained, as long as the resulting data is greater than 0, we can judge whether a data is in the given set.

Of course, there is a precondition here that the high bit of the data we input is always 1, which is a power of 2.

The same optimization is used when parsing query syntax:

Other kinky tricks

Of course, there are some other tricks for bit operations, such as judging parity:

 // 偶数
a & 1 == 0

// 奇数
a & 1 == 1

Multiplication and division, right shift by 1 is divided by 2, left shifted by one is multiplied by 2.

 x := 2
fmt.Println(x>>1) //1
fmt.Println(x<<1) //4

Summarize

Bit operations not only improve program performance, but also reduce code readability, so we have to choose whether to use it or not;

Some low-level libraries and framework codes are recommended for use in scenarios with extreme pursuit of performance. However, it is not necessary to use bit operations to add, subtract, multiply and divide data in business code, which will only make subsequent maintainers confused.

Relevant code: https://github.com/crossoverJie/xjson


crossoverJie
5.4k 声望4k 粉丝