fastjson icon indicating copy to clipboard operation
fastjson copied to clipboard

how to use func (*Value) Set

Open Arnold1 opened this issue 6 years ago • 21 comments

Hi,

i have the following, how can i add a key/value to the json?

// mapping.data is []*fastjson.Value

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   val.Set("ID", fastjson.MustParse(settings.ID))
}

i currently get: panic: cannot parse JSON: cannot parse empty string; unparsed tail: ""

Arnold1 avatar Jul 16 '19 03:07 Arnold1

settings.ID must contain valid JSON if it is passed to fastjson.MustParse. An empty string isn't valid JSON. It must be wrapped into double quotes to become valid JSON.

valyala avatar Jul 16 '19 10:07 valyala

@valyala oh, my bad. i need to do:

// mapping.data is []*fastjson.Value

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   if settings != nil {
       val.Set("ID", fastjson.MustParse("\"" + settings.ID + "\""))
   }
}

maybe its also to do error handling by myself (using: fastjson.Parse) and not use fastjson.MustParse...

@valyala what happens if settings.ID is a int64 and i want the value to be an integer and not string? val.Set("ID", settings.ID)

Arnold1 avatar Jul 16 '19 14:07 Arnold1

@valyala what happens if settings.ID is a int64 and i want the value to be an integer and not string? val.Set("ID", settings.ID)

Arnold1 avatar Jul 23 '19 18:07 Arnold1

See https://godoc.org/github.com/valyala/fastjson#Arena.NewNumberInt .

valyala avatar Jul 24 '19 23:07 valyala

@valyala

before:

val.Set("ID", fastjson.MustParse("\""+strconv.FormatUint(settings.ID, 10)+"\""))

how to use NewNumberInt here? how to use Arena for that? may i ask why uses arena compared to fastjson.MustParse?

val.Set("ID", fastjson.NewNumberInt(settings.ID))

Arnold1 avatar Jul 28 '19 11:07 Arnold1

@valyala not sure how to use arena, its kinda strange to me... how to pass it to existing val.Set ?

Arnold1 avatar Jul 29 '19 12:07 Arnold1

The following code shows basic usage for Arena:

var a fastjson.Arena
val.Set("ID", a.NewNumberInt(settings.ID))

This usage isn't optimal from performance point of view, since the Arena isn't re-used. This means it allocates memory with each NewNumberInt call. The optimal usage is quite sophisticated - read Arena docs.

valyala avatar Jul 29 '19 15:07 valyala

can you also do that?

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   if settings != nil {
      var a fastjson.Arena
      val.Set("ID", a.NewNumberInt(settings.ID))
      val.Set("ID2", a.NewNumberString(settings.ID2))
   }
}

...will that reallocate each time?

also are you allowed to do the following?

// mapping.data is []*fastjson.Value

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   if settings != nil {
       val.Set("ID", fastjson.MustParse("\"" + settings.ID + "\""))
       val.Set("ID2", fastjson.MustParse("\"" + settings.ID2 + "\""))
   }
}

fastjson.MustParse/fastjson.Parse ... Parse parses s containing JSON. The returned value is valid until the next call to Parse*.

Arnold1 avatar Jul 30 '19 17:07 Arnold1

can you also do that?

Yes. It is better to allocate the arena only once before the loop:

var a fastjson.Arena
for _, val := range mapping.data {
   // ...
}

also are you allowed to do the following?

Yes, this is OK, since fastjson.Parse and fastjson.MustParse allocates memory for the returned value. The The returned value is valid until the next call to Parse*. comment corresponds to fastjson.Parser.Parse, which re-uses the returned object on the next call.

The best approach to use fastjson is the following:

var pp fastjson.ParserPool
var ap fastjson.ArenaPool

// ParseAndProcessJSON parses JSON in s and processes it in zero-allocation mode.
// It doesn't hold any values returned from `fastjson.Parser.Parse` call.
func ParseAndProcessJSON(s string) error {
  p := pp.Get()
  defer pp.Put(p)
  v, err := p.Parse(s)
  if err != nil {
    return fmt.Errorf("cannot parse JSON %q: %s", s, err)
  }

  // Get an arena from pool for allocating objects for v inside processJSON below.
  a := ap.Get()
  defer ap.Put(a)

  // processJSON shouldn't hold any references to v, to child objects of v and to a.
  // Otherwise data races will occur in the following cases:
  //    * when Parse is called on the re-used `p`
  //    * when `a` is re-used
  if err := processJSON(v, a); err != nil {
    return fmt.Errorf("cannot process JSON from %q: %s", s, err)
  }
  return nil
}

valyala avatar Jul 30 '19 19:07 valyala

do you suggest to define var pp fastjson.ParserPool and var ap fastjson.ArenaPool global?

how many elements does it preallocate for fastjson.ArenaPool and fastjson.ArenaPool? dont you need to check if ap.Get() is nil in case all elements are used from pool?

what happens if ParseAndProcessJSON gets called by multiple go routines?

Arnold1 avatar Jul 30 '19 22:07 Arnold1

do you suggest to define var pp fastjson.ParserPool and var ap fastjson.ArenaPool global?

yes

how many elements does it preallocate for fastjson.ArenaPool and fastjson.ArenaPool? dont you need to check if ap.Get() is nil in case all elements are used from pool?

Pools don't pre-allocate elements - the elements are allocated when Get is called and the pool is empty.

what happens if ParseAndProcessJSON gets called by multiple go routines?

It should just work if processJSON function adheres rules described in comments above the call.

valyala avatar Jul 30 '19 23:07 valyala

@valyala

i call defer pp.Put(p) in ParseAndProcessJSON question: can i still iterate over sendData *fastjson.Value in sendData and call req.SetBody(val.String()) ?

var pp fastjson.ParserPool
var ap fastjson.ArenaPool

func processJSON(a, v, mapping) error {
    for _, val := range mapping.data {
       settings := getSettings(val.Get("Type"))

       if settings != nil {
           val.Set("ID", a.NewString(settings.ID)) // here i use a from fastjson.ArenaPool
           val.Set("ID2", a.NewString(settings.ID2)) // here i use a from fastjson.ArenaPool
       }
    }
}

// ParseAndProcessJSON parses JSON in s and processes it in zero-allocation mode.
// It doesn't hold any values returned from `fastjson.Parser.Parse` call.
func ParseAndProcessJSON(s string, mapping) error {
  // allocate mapping

  p := pp.Get()
  defer pp.Put(p)

  v, err := p.Parse(s)
  if err != nil {
    return fmt.Errorf("cannot parse JSON %q: %s", s, err)
  }

  // Get an arena from pool for allocating objects for v inside processJSON below.
  a := ap.Get()
  defer ap.Put(a)

  // processJSON shouldn't hold any references to v, to child objects of v and to a.
  // Otherwise data races will occur in the following cases:
  //    * when Parse is called on the re-used `p`
  //    * when `a` is re-used
  if err := processJSON(v, a, mapping); err != nil {
    return fmt.Errorf("cannot process JSON from %q: %s", s, err)
  }
  return nil
}


func bidRequestRoute(ctx *fasthttp.RequestCtx) error {
    mapping := ParseAndProcessJSON(ctx) // i do defer pp.Put(p) here, question: can i still iterate over sendData *fastjson.Value in sendData?

    var wg sync.WaitGroup
    for _, val := range mapping {
        wg.Add(1)
        go sendData(data)
    }
  
   wg.Wait()

   // read sendData response...
   // build reponse to ctx request...
   ctx.SetStatusCode(fasthttp.StatusOK)
}

Arnold1 avatar Jul 31 '19 14:07 Arnold1

question: can i still iterate over sendData *fastjson.Value in sendData and call req.SetBody(val.String()) ?

This is unsafe, since the parser that owns the returned value has been put back to the pool. This means that concurrent goroutine can obtain it from the pool and call Parse on it while the current goroutine iterates over the mapping. All the iteration and json value processing must be performed inside processJSON function.

valyala avatar Jul 31 '19 17:07 valyala

can i move the .Get and .Put right before mapping := ParseAndProcessJSON(ctx) - inside func bidRequestRoute(ctx *fasthttp.RequestCtx) error {? that would fix it right?

Arnold1 avatar Jul 31 '19 21:07 Arnold1

I'm sorry, but I don't follow what do you mean. Could you provide example code?

valyala avatar Aug 01 '19 22:08 valyala

@valyala i have the following benchmark, any idea how it fails inside the for loop at iteration 512?!

func BenchmarkTestConsumerMappingUUIDSplitTest2(b *testing.B) {
	expectedConsumerLength := 1
	expectedEntitiesLength := 1
	exchangeID := "1" // default consumer doesnt care about exchangeID

	cMapAV, err := loadCampaignStore("campaignStoreApiTestData3")
	require.NoErr(err)

	getCMap := multiConsumer(
		newDefaultTestConsumer())

	uuid := []byte("6a191e5f-9a6e-0405-d6ba-73f722fe0131")
	cid := []byte("6a191e5f-9a6e-0405-d6ba-73f722fe0131")

	b.ResetTimer()
	fmt.Println("b.N:", b.N)

	for i := 0; i < b.N; i++ {
		parsed, err := fastjson.ParseBytes(loadTestJSON("sendData"))
		require.NoErr(err)

		fmt.Println("i:", i)

		a := global.fastJSONArenaPool.Get()

		bidRequestPieces, mapping := getConsumerMappings(a, parsed, global.defaultConsumerName, exchangeID, 0, getCMap(), uuid, cid, getCampaignSettingMapFunc(cMapAV))
		require.NotNil(bidRequestPieces)
		require.Len(mapping, expectedConsumerLength)
		hashedName := hashIt(global.defaultConsumerName)
		require.Len(mapping[hashedName].selectedEntities, expectedEntitiesLength)

		a.Reset()
		global.fastJSONArenaPool.Put(a)
	}
}

output:

$  go test -run=x -bench=. -benchmem
2019/08/15 09:11:41 main.go:331: Loading with 8 CPUS
goos: darwin
goarch: amd64
BenchmarkTestSendingBatchesRetries-8           	       2	 526029726 ns/op	  316412 B/op	    1225 allocs/op
b.N: 1
i: 0
BenchmarkTestConsumerMappingUUIDSplitTest2-8   	b.N: 100
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
i: 10
i: 11
i: 12
i: 13
i: 14
i: 15
i: 16
i: 17
i: 18
i: 19
i: 20
i: 21
i: 22
i: 23
i: 24
i: 25
i: 26
i: 27
i: 28
i: 29
i: 30
i: 31
i: 32
i: 33
i: 34
i: 35
i: 36
i: 37
i: 38
i: 39
i: 40
i: 41
i: 42
i: 43
i: 44
i: 45
i: 46
i: 47
i: 48
i: 49
i: 50
i: 51
i: 52
i: 53
i: 54
i: 55
i: 56
i: 57
i: 58
i: 59
i: 60
i: 61
i: 62
i: 63
i: 64
i: 65
i: 66
i: 67
i: 68
i: 69
i: 70
i: 71
i: 72
i: 73
i: 74
i: 75
i: 76
i: 77
i: 78
i: 79
i: 80
i: 81
i: 82
i: 83
i: 84
i: 85
i: 86
i: 87
i: 88
i: 89
i: 90
i: 91
i: 92
i: 93
i: 94
i: 95
i: 96
i: 97
i: 98
i: 99
b.N: 5000
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
i: 10
i: 11
i: 12
i: 13
i: 14
i: 15
i: 16
i: 17
i: 18
i: 19
i: 20
i: 21
i: 22
i: 23
i: 24
i: 25
i: 26
i: 27
i: 28
i: 29
i: 30
i: 31
i: 32
i: 33
i: 34
i: 35
i: 36
i: 37
i: 38
i: 39
i: 40
i: 41
i: 42
i: 43
i: 44
i: 45
i: 46
i: 47
i: 48
i: 49
i: 50
i: 51
i: 52
i: 53
i: 54
i: 55
i: 56
i: 57
i: 58
i: 59
i: 60
i: 61
i: 62
i: 63
i: 64
i: 65
i: 66
i: 67
i: 68
i: 69
i: 70
i: 71
i: 72
i: 73
i: 74
i: 75
i: 76
i: 77
i: 78
i: 79
i: 80
i: 81
i: 82
i: 83
i: 84
i: 85
i: 86
i: 87
i: 88
i: 89
i: 90
i: 91
i: 92
i: 93
i: 94
i: 95
i: 96
i: 97
i: 98
i: 99
i: 100
i: 101
i: 102
i: 103
i: 104
i: 105
i: 106
i: 107
i: 108
i: 109
i: 110
i: 111
i: 112
i: 113
i: 114
i: 115
i: 116
i: 117
i: 118
i: 119
i: 120
i: 121
i: 122
i: 123
i: 124
i: 125
i: 126
i: 127
i: 128
i: 129
i: 130
i: 131
i: 132
i: 133
i: 134
i: 135
i: 136
i: 137
i: 138
i: 139
i: 140
i: 141
i: 142
i: 143
i: 144
i: 145
i: 146
i: 147
i: 148
i: 149
i: 150
i: 151
i: 152
i: 153
i: 154
i: 155
i: 156
i: 157
i: 158
i: 159
i: 160
i: 161
i: 162
i: 163
i: 164
i: 165
i: 166
i: 167
i: 168
i: 169
i: 170
i: 171
i: 172
i: 173
i: 174
i: 175
i: 176
i: 177
i: 178
i: 179
i: 180
i: 181
i: 182
i: 183
i: 184
i: 185
i: 186
i: 187
i: 188
i: 189
i: 190
i: 191
i: 192
i: 193
i: 194
i: 195
i: 196
i: 197
i: 198
i: 199
i: 200
i: 201
i: 202
i: 203
i: 204
i: 205
i: 206
i: 207
i: 208
i: 209
i: 210
i: 211
i: 212
i: 213
i: 214
i: 215
i: 216
i: 217
i: 218
i: 219
i: 220
i: 221
i: 222
i: 223
i: 224
i: 225
i: 226
i: 227
i: 228
i: 229
i: 230
i: 231
i: 232
i: 233
i: 234
i: 235
i: 236
i: 237
i: 238
i: 239
i: 240
i: 241
i: 242
i: 243
i: 244
i: 245
i: 246
i: 247
i: 248
i: 249
i: 250
i: 251
i: 252
i: 253
i: 254
i: 255
i: 256
i: 257
i: 258
i: 259
i: 260
i: 261
i: 262
i: 263
i: 264
i: 265
i: 266
i: 267
i: 268
i: 269
i: 270
i: 271
i: 272
i: 273
i: 274
i: 275
i: 276
i: 277
i: 278
i: 279
i: 280
i: 281
i: 282
i: 283
i: 284
i: 285
i: 286
i: 287
i: 288
i: 289
i: 290
i: 291
i: 292
i: 293
i: 294
i: 295
i: 296
i: 297
i: 298
i: 299
i: 300
i: 301
i: 302
i: 303
i: 304
i: 305
i: 306
i: 307
i: 308
i: 309
i: 310
i: 311
i: 312
i: 313
i: 314
i: 315
i: 316
i: 317
i: 318
i: 319
i: 320
i: 321
i: 322
i: 323
i: 324
i: 325
i: 326
i: 327
i: 328
i: 329
i: 330
i: 331
i: 332
i: 333
i: 334
i: 335
i: 336
i: 337
i: 338
i: 339
i: 340
i: 341
i: 342
i: 343
i: 344
i: 345
i: 346
i: 347
i: 348
i: 349
i: 350
i: 351
i: 352
i: 353
i: 354
i: 355
i: 356
i: 357
i: 358
i: 359
i: 360
i: 361
i: 362
i: 363
i: 364
i: 365
i: 366
i: 367
i: 368
i: 369
i: 370
i: 371
i: 372
i: 373
i: 374
i: 375
i: 376
i: 377
i: 378
i: 379
i: 380
i: 381
i: 382
i: 383
i: 384
i: 385
i: 386
i: 387
i: 388
i: 389
i: 390
i: 391
i: 392
i: 393
i: 394
i: 395
i: 396
i: 397
i: 398
i: 399
i: 400
i: 401
i: 402
i: 403
i: 404
i: 405
i: 406
i: 407
i: 408
i: 409
i: 410
i: 411
i: 412
i: 413
i: 414
i: 415
i: 416
i: 417
i: 418
i: 419
i: 420
i: 421
i: 422
i: 423
i: 424
i: 425
i: 426
i: 427
i: 428
i: 429
i: 430
i: 431
i: 432
i: 433
i: 434
i: 435
i: 436
i: 437
i: 438
i: 439
i: 440
i: 441
i: 442
i: 443
i: 444
i: 445
i: 446
i: 447
i: 448
i: 449
i: 450
i: 451
i: 452
i: 453
i: 454
i: 455
i: 456
i: 457
i: 458
i: 459
i: 460
i: 461
i: 462
i: 463
i: 464
i: 465
i: 466
i: 467
i: 468
i: 469
i: 470
i: 471
i: 472
i: 473
i: 474
i: 475
i: 476
i: 477
i: 478
i: 479
i: 480
i: 481
i: 482
i: 483
i: 484
i: 485
i: 486
i: 487
i: 488
i: 489
i: 490
i: 491
i: 492
i: 493
i: 494
i: 495
i: 496
i: 497
i: 498
i: 499
i: 500
i: 501
i: 502
i: 503
i: 504
i: 505
i: 506
i: 507
i: 508
i: 509
i: 510
i: 511
i: 512
2019/08/15 09:11:46 require.go:57: map[]
2019/08/15 09:11:46 require.go:20: 
	File: consumerMapping_test.go:156 // this is line: require.Len(mapping, expectedConsumerLength)
	Got: 0
	Expected: 1
exit status 1

Arnold1 avatar Aug 15 '19 13:08 Arnold1

It is hard to determine the root cause of the issue without the contents of loadTestJSON and getConsumerMappings function bodies. Try moving loadTestJSON call outside the loop.

valyala avatar Aug 15 '19 17:08 valyala

it was an issue with my test :)

Arnold1 avatar Aug 21 '19 12:08 Arnold1

@valyala i try to to the following but i dont get back the expected result. any idea what i do wrong?

func readInt32(b []byte) int32 {
	return int32(binary.LittleEndian.Uint32(b))
	// equivalnt of return int32(binary.LittleEndian.Uint32(b))
	//return int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
}

func BenchmarkParsing(b *testing.B) {
	var p fastjson.Parser
	v, err := p.Parse(`{
        "ID": "12345"
	}`)
	require.Nil(err)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		id := readInt32(v.GetStringBytes("ID"))
		require.Nil(err)
		require.Equal(id, int32(12345))
	}
}

Arnold1 avatar Aug 24 '19 12:08 Arnold1

You need to use strconv.ParseInt instead of binary.LittleEndian.Uint32.

valyala avatar Aug 25 '19 12:08 valyala

@valyala ok my bad.

Arnold1 avatar Aug 26 '19 10:08 Arnold1