diff --git a/internal/confgen/parallel.go b/internal/confgen/parallel.go new file mode 100644 index 00000000..4acda3a5 --- /dev/null +++ b/internal/confgen/parallel.go @@ -0,0 +1,516 @@ +package confgen + +import ( + "context" + "errors" + "hash/fnv" + "runtime" + + "github.com/tableauio/tableau/internal/confgen/fieldprop" + "github.com/tableauio/tableau/internal/importer/book" + "github.com/tableauio/tableau/internal/importer/book/tableparser" + "github.com/tableauio/tableau/internal/x/xerrors" + "github.com/tableauio/tableau/internal/x/xpool" + "github.com/tableauio/tableau/internal/x/xproto" + "github.com/tableauio/tableau/log" + "github.com/tableauio/tableau/proto/tableaupb" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// minParallelRows is the threshold below which parallelism is skipped: +// the per-goroutine setup cost is likely to outweigh the savings. +const minParallelRows = 1024 + +// workerSem caps the total number of concurrently running row-parsing +// workers across all sheets at GOMAXPROCS. Without it, with N sheets +// parsing in parallel the live worker count could reach N*GOMAXPROCS +// and trigger scheduler thrash. +// +// Scoped to leaf workers only -- callers above parseTableInParallel +// (e.g. gen.convert) must not share this semaphore, otherwise an outer +// goroutine holding a token would deadlock waiting for its own inner +// workers. +var workerSem = xpool.NewCPUSemaphore() + +// parallelStrategy selects how parseTableInParallel partitions the data rows +// across workers. The strategy is decided once by canParallelizeSheet -- it is +// a property of the sheet's top-level field shape, not of the row values. +type parallelStrategy int + +const ( + // strategyRowRange splits rows into contiguous half-open intervals. Used + // when the top-level field is a vertical NON-keyed list of message: + // every row produces a fresh element via List.Append, so block boundaries + // are independent and the merge step concatenates blocks in row order. + strategyRowRange parallelStrategy = iota + // strategyKeyHash routes each row to a worker by hashing the raw cell + // string of the top-level outer-key column. Used when the top-level + // field is a vertical map OR a vertical KEYED list. Rows sharing the + // same outer key always land in the same worker, so the worker's serial + // path handles cross-row aggregation (multi-level vertical map nesting, + // keyed-list row merging) exactly as it would in the non-parallel run. + // Cross-block duplicate keys cannot occur by construction; the merge + // step's duplicate-key check is retained as a defensive invariant. + strategyKeyHash +) + +// parallelPlan is the output of canParallelizeSheet's eligibility check. When +// the sheet is eligible (`ok == true`), `strategy` and (for key-hash mode) +// `keyColumn` describe how parseTableInParallel should shard the rows. +type parallelPlan struct { + ok bool + strategy parallelStrategy + keyColumn string // header column name; empty when strategy != strategyKeyHash + reason string // human-readable rejection reason; empty when ok +} + +// canParallelizeSheet reports whether the given sheet is eligible for parallel +// table parsing. The sheet must satisfy ALL of the following conditions: +// +// 1. It is a table sheet (Excel/CSV), not transposed. +// 2. AdjacentKey is not enabled (cross-row key auto-population would break +// when the data rows are split into independent blocks). +// 3. The top-level message has exactly one field, which is one of: +// a. A vertical map whose value is a message. Rows sharing the same +// outer key are routed to the same worker (strategyKeyHash), so +// the worker's serial path handles uniqueness checks AND multi- +// level cross-row aggregation just like the non-parallel run. +// b. A vertical KEYED list whose element is a message. Same routing +// as (a) -- the key column drives the partition. +// c. A vertical NON-keyed list whose element is a message. Each row +// contributes one fresh list element (strategyRowRange); blocks +// are concatenated in order at the merge step. +// 4. No field in the descriptor (recursively) declares Unique/Sequence/ +// Order/Aggregate properties: those validations need whole-sheet +// visibility and would mis-report on per-shard inputs. +// +// When the function returns ok=false, `reason` is a human-readable rejection +// suitable for a debug log explaining why parallel mode was skipped. +func canParallelizeSheet(p *sheetParser, md protoreflect.MessageDescriptor) parallelPlan { + if !p.IsTable() { + return parallelPlan{reason: "not a table sheet"} + } + if p.sheetOpts.GetTranspose() { + return parallelPlan{reason: "transpose is enabled"} + } + if p.sheetOpts.GetAdjacentKey() { + return parallelPlan{reason: "adjacent_key is enabled"} + } + + fields := md.Fields() + if fields.Len() != 1 { + return parallelPlan{reason: "top-level message must have exactly one field"} + } + topFd := fields.Get(0) + topField := p.parseFieldDescriptor(topFd) + defer topField.release() + + switch { + case topFd.IsMap(): + if parseTableMapLayout(topField.opts.GetLayout()) != tableaupb.Layout_LAYOUT_VERTICAL { + return parallelPlan{reason: "top-level map layout is not vertical"} + } + valueFd := topFd.MapValue() + if valueFd.Kind() != protoreflect.MessageKind { + return parallelPlan{reason: "top-level map value is not a message"} + } + valueMd := valueFd.Message() + // We DO allow !deduceKeyUnique here: that's exactly the multi-level + // vertical-map case where same outer key rows aggregate into one + // entry. Hash-by-key partitioning lands those rows in the same + // worker, where the serial path handles aggregation. We still + // reject explicit RequireUnique sub-fields below in walkParallelSafe. + keyName := topField.opts.GetKey() + if keyName == "" { + return parallelPlan{reason: "top-level vertical map has empty key"} + } + // opts.Key is the *column name* (e.g. "HeroID"); resolve it the + // same way the serial path does so we get a real "key field not + // found" rejection before we try to hash on a non-existent column. + if p.findFieldByName(valueMd, keyName) == nil { + return parallelPlan{reason: "map key field " + keyName + " not found in value message"} + } + if reason := checkParallelSafeMessage(p, valueMd); reason != "" { + return parallelPlan{reason: reason} + } + return parallelPlan{ok: true, strategy: strategyKeyHash, keyColumn: keyName} + + case topFd.IsList(): + if parseTableListLayout(topField.opts.GetLayout()) != tableaupb.Layout_LAYOUT_VERTICAL { + return parallelPlan{reason: "top-level list layout is not vertical"} + } + if topFd.Kind() != protoreflect.MessageKind { + // Scalar vertical lists are not supported by the parser anyway, + // but be explicit here. + return parallelPlan{reason: "top-level list element is not a message"} + } + elemMd := topFd.Message() + keyName := topField.opts.GetKey() + if reason := checkParallelSafeMessage(p, elemMd); reason != "" { + return parallelPlan{reason: reason} + } + if keyName == "" { + // Non-keyed vertical list: each row produces a fresh element, + // row-range sharding is correct and trivially preserves order. + return parallelPlan{ok: true, strategy: strategyRowRange} + } + // Keyed vertical list: rows sharing the same key are merged into + // one element (just like a vertical map). Hash by key column so + // same-key rows land in the same worker. + if p.findFieldByName(elemMd, keyName) == nil { + return parallelPlan{reason: "list key field " + keyName + " not found in element message"} + } + return parallelPlan{ok: true, strategy: strategyKeyHash, keyColumn: keyName} + + default: + return parallelPlan{reason: "top-level field is neither a map nor a list"} + } +} + +// checkParallelSafeMessage walks a message descriptor and returns a non-empty +// reason string if any sub-field disqualifies the sheet from parallel mode. +// +// The walk rejects whole-sheet validation properties (Unique/Sequence/Order/ +// Aggregate) anywhere in the descriptor tree -- those need a global view of +// the data that sharding cannot provide. It does NOT reject nested vertical +// maps/lists: such fields aggregate rows that share an ancestor outer key, +// and the strategyKeyHash partitioning routes those rows to a single worker, +// so the serial path's aggregation logic still applies inside each shard. +func checkParallelSafeMessage(p *sheetParser, md protoreflect.MessageDescriptor) string { + visited := map[protoreflect.FullName]bool{} + return walkParallelSafe(p, md, visited) +} + +func walkParallelSafe(p *sheetParser, md protoreflect.MessageDescriptor, visited map[protoreflect.FullName]bool) string { + if visited[md.FullName()] { + return "" + } + visited[md.FullName()] = true + + fields := md.Fields() + for i := 0; i < fields.Len(); i++ { + fd := fields.Get(i) + field := p.parseFieldDescriptor(fd) + opts := field.opts + prop := opts.GetProp() + // Whole-sheet validations would yield wrong results when blocks are + // merged: each shard sees only a subset of values. + if fieldprop.RequireUnique(prop) { + field.release() + return "field " + string(fd.FullName()) + " requires Unique" + } + if fieldprop.RequireSequence(prop) { + field.release() + return "field " + string(fd.FullName()) + " requires Sequence" + } + if fieldprop.RequireOrder(prop) { + field.release() + return "field " + string(fd.FullName()) + " requires Order" + } + if prop.GetAggregate() { + field.release() + return "field " + string(fd.FullName()) + " enables Aggregate" + } + field.release() + // Recurse into message-typed sub-fields, message list elements, and + // message map values. Map-key types are always scalar/enum so don't + // need recursion. + switch { + case fd.IsMap(): + if valueFd := fd.MapValue(); valueFd.Kind() == protoreflect.MessageKind { + if reason := walkParallelSafe(p, valueFd.Message(), visited); reason != "" { + return reason + } + } + case fd.IsList(): + if fd.Kind() == protoreflect.MessageKind { + if reason := walkParallelSafe(p, fd.Message(), visited); reason != "" { + return reason + } + } + case fd.Kind() == protoreflect.MessageKind: + if reason := walkParallelSafe(p, fd.Message(), visited); reason != "" { + return reason + } + } + } + return "" +} + +// parseTableInParallel parses the table by sharding its data rows across +// `runtime.GOMAXPROCS(0)` goroutines, then merges the partial messages with +// xproto.Merge. +// +// Caller must have verified eligibility via canParallelizeSheet first and +// pass the resulting plan in. +func (p *tableParser) parseTableInParallel(protomsg proto.Message, table book.Tabler, plan parallelPlan) error { + header := tableparser.NewHeader(p.sheetOpts, p.bookOpts, nil) + beginRow, endRow := tableparser.DataRowRange(table, header) + totalRows := endRow - beginRow + workers := runtime.GOMAXPROCS(0) + if workers < 2 || totalRows < minParallelRows { + return p.parse(protomsg, table) + } + // Cap workers by row count to avoid empty blocks. + if workers > totalRows { + workers = totalRows + } + log.Debugf("parallel confgen: sheet=%s rows=%d workers=%d strategy=%d", + p.sheetOpts.GetName(), totalRows, workers, plan.strategy) + + shards, err := planShards(table, header, plan, beginRow, endRow, workers) + if err != nil { + return err + } + // `planShards` may return fewer entries than `workers` (key-hash mode + // can leave some buckets empty under skew). Skip empty shards entirely. + activeShards := make([]shardWork, 0, len(shards)) + for _, s := range shards { + if len(s.indices) > 0 || s.kind == shardKindRange { + activeShards = append(activeShards, s) + } + } + if len(activeShards) <= 1 { + // Either every row hashed into a single bucket, or only one + // non-empty range exists. Either way, parallelism would buy nothing + // and the overhead is pure cost. Fall back to the serial path. + return p.parse(protomsg, table) + } + + // Each worker produces its own message, so the parsers operate on + // disjoint state and need no synchronization. We use proto.Clone on the + // destination message rather than dynamicpb.NewMessage, so partials share + // the concrete Go type with protomsg and can be merged via xproto.Merge + // without runtime type errors when the caller passes a typed message. + partials := make([]proto.Message, len(activeShards)) + parsers := make([]*tableParser, len(activeShards)) + for i := range parsers { + // Clone the underlying sheetParser per worker; tableParser holds + // per-sheet caches (cards, sheetCollector child) that are not + // goroutine-safe. + sp := newWorkerSheetParser(p.sheetParser) + parsers[i] = &tableParser{sheetParser: sp} + partials[i] = proto.Clone(protomsg) + // proto.Clone copies fields; reset to ensure each worker starts empty. + proto.Reset(partials[i]) + } + + g := p.sheetCollector.NewGroup(context.Background()) + for i := range activeShards { + idx := i + shard := activeShards[idx] + g.Go(func(ctx context.Context) error { + // Gate the heavy row-parsing phase on the global worker budget; + // dispatch is unaffected since each shard still gets its own goroutine. + if err := workerSem.Acquire(ctx); err != nil { + return err + } + defer workerSem.Release() + worker := parsers[idx] + partial := partials[idx] + msg := partial.ProtoReflect() + rowFn := func(r *book.Row) error { + if worker.sheetCollector.IsFull() { + return worker.sheetCollector.Join() + } + _, rowErr := worker.parseMessage(nil, msg, r, "", "") + if rowErr != nil { + if cerr := worker.sheetCollector.Collect(rowErr); cerr != nil { + return cerr + } + } + return nil + } + var err error + switch shard.kind { + case shardKindRange: + err = tableparser.RangeDataRowsInBlock(table, header, shard.beginRow, shard.endRow, rowFn) + case shardKindIndices: + err = tableparser.RangeDataRowsByIndices(table, header, shard.indices, rowFn) + } + if err != nil { + return err + } + if worker.sheetCollector.HasErrors() { + return worker.sheetCollector.Join() + } + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + + // Reduce partials by ascending shard index so the merged result matches + // serial row order: + // - list : elements appended shard-by-shard. For row-range strategy + // this preserves sheet order. For key-hash strategy the + // order is "all rows of one bucket, then the next" -- for + // keyed lists the per-key element identity (and per-element + // append order within each shard) is preserved, which is + // what callers care about. + // - map : same-key rows always landed in the same shard, so cross- + // shard duplicate keys are an invariant violation, not a + // user-facing error. xproto.mergeMap's existing duplicate- + // key check is retained as a defensive assertion. + // Note: g.Wait() is a barrier above, so this reduction is independent of + // the goroutines' completion order. + for _, partial := range partials { + if err := xproto.Merge(protomsg, partial); err != nil { + if errors.Is(err, xproto.ErrDuplicateKey) { + // This indicates a partitioning bug (same outer key reached + // two shards), NOT a user data error. Surface it as an + // internal error rather than the user-facing E2005 so it's + // not mistaken for "user wrote duplicate key on a unique + // field" -- the user-facing duplicate is already handled by + // the worker's serial path on its own shard. + return xerrors.Newf("internal: duplicate map key across parallel shards (partition bug)") + } + return err + } + } + return nil +} + +// shardKind tags the row-iteration mode for a single shard. +type shardKind int + +const ( + shardKindRange shardKind = iota + shardKindIndices +) + +// shardWork describes the rows a single worker should process. +type shardWork struct { + kind shardKind + // Range mode (kind == shardKindRange): + beginRow, endRow int + // Indices mode (kind == shardKindIndices): explicit list of row numbers + // in ascending order. + indices []int +} + +// planShards partitions the data rows according to the parallel plan. +// +// For strategyRowRange, rows are divided into `workers` contiguous half-open +// intervals; the leading `remainder` shards each absorb one extra row so the +// total row count is exactly preserved. +// +// For strategyKeyHash, the planner first reads the outer-key column for every +// row, then hashes each non-empty key with FNV-1a and routes the row to +// `hash % workers`. Empty keys (blank cells) deterministically go to bucket 0 +// so that "two blank-key rows under a !deduceKeyUnique map" still merge in +// one worker rather than splitting and tripping the defensive E2005. Rows +// within a bucket retain their ascending sheet order, which is the precondition +// the serial parseVerticalMapField/parseVerticalListField rely on for last- +// write-wins on scalar sub-fields and append-order on inner vertical lists. +func planShards(table book.Tabler, header *tableparser.Header, plan parallelPlan, beginRow, endRow, workers int) ([]shardWork, error) { + totalRows := endRow - beginRow + switch plan.strategy { + case strategyRowRange: + shards := make([]shardWork, workers) + blockSize := totalRows / workers + remainder := totalRows % workers + cursor := beginRow + for i := 0; i < workers; i++ { + size := blockSize + if i < remainder { + size++ + } + shards[i] = shardWork{ + kind: shardKindRange, + beginRow: cursor, + endRow: cursor + size, + } + cursor += size + } + return shards, nil + + case strategyKeyHash: + keys, err := tableparser.ScanColumn(table, header, plan.keyColumn, beginRow, endRow) + if err != nil { + return nil, err + } + shards := make([]shardWork, workers) + for i := range shards { + shards[i] = shardWork{kind: shardKindIndices} + } + // Pre-allocate roughly fairly so we don't pay len(rows) reallocs + // when the bucket distribution is balanced. + guess := totalRows/workers + 4 + for i := range shards { + shards[i].indices = make([]int, 0, guess) + } + for offset, key := range keys { + bucket := bucketForKey(key, workers) + shards[bucket].indices = append(shards[bucket].indices, beginRow+offset) + } + return shards, nil + } + return nil, xerrors.Newf("internal: unknown parallel strategy %d", plan.strategy) +} + +// bucketForKey deterministically maps a raw cell string to a worker index. +// Empty strings collapse into bucket 0 so that all blank-key rows share a +// single shard (otherwise hash(empty) would still pick one bucket but it's +// clearer to make the policy explicit). +// +// KNOWN RISK -- string-level vs type-level key equivalence: +// We hash the raw cell string, not the typed key the parser will eventually +// decode. So two cells that are EQUAL after type parsing but DIFFERENT as +// strings are routed to different buckets. Examples: +// - int64 key: "1" vs "01" vs "+1" vs " 1" -> all decode to int64(1) +// - bool key: "true" vs "True" vs "TRUE" vs "1" +// - float key: "1" vs "1.0" vs "1e0" +// - enum key: "Item_NONE" vs "0" vs "NONE" +// +// When the same logical key is split across workers, vertical map / vertical +// list invariants can break: +// - last-write-wins / append-order are no longer well-defined (depends on +// worker completion order during merge); +// - duplicate-key detection for !deduceKeyUnique maps (E2005) silently +// misses the conflict because each worker only sees one variant. +// Whether two variants collide also depends on the dynamic worker count, so +// the bug can hide on one machine and surface on another. +// +// We accept this risk for now because real configs rarely rely on such +// variants for the SAME logical key within one sheet. Two follow-ups when +// it actually bites: +// 1. Hash the typed key: have ScanColumn decode each cell with the outer- +// key field descriptor and hash a canonical byte form, aligning bucket +// equivalence with merge-time equivalence. +// 2. Defensive merge-time check: after g.Wait, validate uniqueness/order +// by typed key across shards so correctness degrades gracefully (at +// worst toward serial cost) even if planning routed wrong. +func bucketForKey(key string, workers int) int { + if key == "" { + return 0 + } + h := fnv.New32a() + _, _ = h.Write([]byte(key)) + return int(h.Sum32() % uint32(workers)) +} + +// newWorkerSheetParser shallow-clones a sheetParser for use in a parallel +// worker. The clone shares the same context, options, and extInfo (read-only +// shared state), but gets a fresh cards map and a private sheet-level error +// collector so collectors and caches do not race. +func newWorkerSheetParser(src *sheetParser) *sheetParser { + sp := &sheetParser{ + ProtoPackage: src.ProtoPackage, + LocationName: src.LocationName, + ctx: src.ctx, + bookOpts: src.bookOpts, + sheetOpts: src.sheetOpts, + extInfo: src.extInfo, + sheetCollector: src.sheetCollector.NewChild(maxErrorsPerSheet), + } + sp.reset() + return sp +} + +// parallelEligibilityCache was considered, but eligibility depends on +// per-sheet WorksheetOptions (transpose, adjacent_key), so a descriptor-only +// cache is not safe. The walk itself is O(fields) and runs once per sheet, so +// caching is unnecessary. diff --git a/internal/confgen/parallel_test.go b/internal/confgen/parallel_test.go new file mode 100644 index 00000000..3c498b86 --- /dev/null +++ b/internal/confgen/parallel_test.go @@ -0,0 +1,409 @@ +package confgen + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tableauio/tableau/format" + "github.com/tableauio/tableau/internal/importer/book" + "github.com/tableauio/tableau/proto/tableaupb/unittestpb" + "google.golang.org/protobuf/proto" +) + +// newTestSheetParser builds a sheet parser suitable for the parallel-related +// tests below. Parallel confgen is now applied automatically by +// tableParser.Parse whenever the sheet is eligible, so there is no longer a +// per-call toggle: tests that need to compare against the serial reference +// must call tp.parse() directly to bypass the auto-parallel entry point. +func newTestSheetParser(t *testing.T) *sheetParser { + t.Helper() + bookOpts := book.MetabookOptions() + sheetOpts := book.MetasheetOptions(context.Background()) + return NewExtendedSheetParser(context.Background(), "protoconf", "Asia/Shanghai", + bookOpts, sheetOpts, + &SheetParserExtInfo{ + InputDir: "", + SubdirRewrites: map[string]string{}, + BookFormat: format.CSV, + }) +} + +// parseSerial parses `sheet` strictly through the serial path, bypassing the +// auto-parallel dispatch in tableParser.Parse. Used by parallel-vs-serial +// equivalence tests as the reference output. +func parseSerial(t *testing.T, sp *sheetParser, msg proto.Message, sheet *book.Sheet) { + t.Helper() + tp := &tableParser{sheetParser: sp} + require.NoError(t, tp.parse(msg, sheet.Table)) +} + +// parseAuto parses `sheet` through the public Parse entry point, which now +// auto-selects parallel mode whenever the sheet is eligible. This is the +// production code path that all other regression tests should exercise. +func parseAuto(t *testing.T, sp *sheetParser, msg proto.Message, sheet *book.Sheet) { + t.Helper() + require.NoError(t, sp.Parse(msg, sheet)) +} + +func makeItemConfRows(n int) [][]string { + rows := [][]string{ + {"ID", "Num"}, + } + for i := 1; i <= n; i++ { + rows = append(rows, []string{fmt.Sprintf("%d", i), fmt.Sprintf("%d", i*10)}) + } + return rows +} + +// TestParallelTableParser_SimpleMapEqualsSerial verifies that a sheet +// eligible for parallel mode produces the exact same proto message as the +// serial path, even when the row count exceeds the parallel threshold and +// triggers actual goroutine sharding. +// +// Because parallel mode is now auto-enabled, the "parallel" output is +// produced via the ordinary Parse entry point, while the "serial" reference +// is produced by calling tp.parse() directly. A regression that broke either +// the auto-dispatch wiring or the partitioning would surface here as an +// inequality rather than a panic, since both paths consume the same input. +func TestParallelTableParser_SimpleMapEqualsSerial(t *testing.T) { + rows := makeItemConfRows(minParallelRows + 64) + sheet := book.NewTableSheet("ItemConf", rows) + + serial := &unittestpb.ItemConf{} + parseSerial(t, newTestSheetParser(t), serial, sheet) + + parallel := &unittestpb.ItemConf{} + parseAuto(t, newTestSheetParser(t), parallel, sheet) + + require.True(t, proto.Equal(serial, parallel), + "parallel result diverges from serial: serial=%v parallel=%v", serial, parallel) + require.Equal(t, len(rows)-1, len(parallel.GetItemMap())) +} + +// TestParallelTableParser_DuplicateKeyIsReported verifies that duplicate map +// keys for a single-level vertical map (deduceKeyUnique=true) are surfaced +// as an error rather than silently overwritten. +// +// Under hash-by-key sharding, the duplicate ItemID lands in the same worker +// as its original, so the worker's serial parseVerticalMapField path is the +// one that detects the duplicate and emits E2005. This also pins down the +// invariant "auto-parallel does not relax single-key uniqueness": users +// relying on E2005 to catch typo'd config sheets must continue to see it. +func TestParallelTableParser_DuplicateKeyIsReported(t *testing.T) { + rows := makeItemConfRows(minParallelRows + 64) + // Inject a duplicate key. Under hash-by-key, this row hashes into the + // same shard as the original "1" row, so the worker's serial path + // triggers E2005. Under the legacy row-range scheme it would have + // triggered xproto.ErrDuplicateKey at the reduce step instead. + rows = append(rows, []string{"1", "999"}) + sheet := book.NewTableSheet("ItemConf", rows) + + got := &unittestpb.ItemConf{} + require.Error(t, newTestSheetParser(t).Parse(got, sheet)) +} + +// TestParallelTableParser_BelowThresholdFallsBack verifies that small sheets +// fall back to the serial path even when eligible, so the per-goroutine +// setup cost does not regress small-sheet performance. +func TestParallelTableParser_BelowThresholdFallsBack(t *testing.T) { + rows := makeItemConfRows(8) // far below minParallelRows + sheet := book.NewTableSheet("ItemConf", rows) + + got := &unittestpb.ItemConf{} + parseAuto(t, newTestSheetParser(t), got, sheet) + require.Len(t, got.GetItemMap(), 8) +} + +// TestParallelTableParser_ListPreservesRowOrder pins down the contract that +// a vertical list of message, when sharded across workers, ends up with its +// elements in the exact same order as the input rows. Concretely: +// +// - parseTableInParallel divides rows into contiguous, ascending blocks; +// - each worker's partial list reflects its block's row order; +// - xproto.mergeList appends partial lists in slice index order +// (i.e. block index order) at the final reduction step. +// +// We bypass canParallelizeSheet here because the only readily-available +// vertical-list-of-message sheet in unittestpb has Unique props (which the +// eligibility check rejects). The Unique check itself is a per-row sheet +// validator that still works under sharding as long as input keys are +// globally unique, which they are by construction below. +func TestParallelTableParser_ListPreservesRowOrder(t *testing.T) { + const n = minParallelRows + 64 + rows := [][]string{ + {"ID", "Name", "Desc"}, + } + for i := 1; i <= n; i++ { + rows = append(rows, []string{ + fmt.Sprintf("%d", i), + fmt.Sprintf("name-%d", i), + fmt.Sprintf("desc-%d", i), + }) + } + sheet := book.NewTableSheet("UniqueFieldInVerticalStructList", rows) + + sp := newTestSheetParser(t) + tp := &tableParser{sheetParser: sp} + got := &unittestpb.UniqueFieldInVerticalStructList{} + require.NoError(t, tp.parseTableInParallel(got, sheet.Table, parallelPlan{ + ok: true, + strategy: strategyRowRange, + })) + + items := got.GetItemList() + require.Len(t, items, n) + for i, it := range items { + require.Equalf(t, uint32(i+1), it.GetId(), + "list element %d out of order: got id=%d", i, it.GetId()) + } +} + +// TestCanParallelizeSheet exhaustively walks the eligibility predicate. +func TestCanParallelizeSheet(t *testing.T) { + tests := []struct { + name string + setup func(p *sheetParser) + md proto.Message + wantOK bool + wantStrategy parallelStrategy // checked only when wantOK + wantSub string // substring of the rejection reason; empty when wantOK + }{ + { + name: "simple vertical map sheet is eligible (key-hash)", + md: &unittestpb.ItemConf{}, + wantOK: true, + wantStrategy: strategyKeyHash, + }, + { + name: "transpose disqualifies", + setup: func(p *sheetParser) { + p.sheetOpts.Transpose = true + }, + md: &unittestpb.ItemConf{}, + wantOK: false, + wantSub: "transpose", + }, + { + name: "adjacent_key disqualifies", + setup: func(p *sheetParser) { + p.sheetOpts.AdjacentKey = true + }, + md: &unittestpb.ItemConf{}, + wantOK: false, + wantSub: "adjacent_key", + }, + { + // MallConf has shop_map (vertical) -> goods_map (vertical). + // This is the canonical multi-level vertical-map case: same + // outer ShopID across different rows aggregates inner goods. + // Hash-by-key partitioning routes same-key rows to the same + // worker, so the worker's serial path runs the same + // aggregation it would in the non-parallel run. No whole-sheet + // validation kicks in (no Unique/Sequence/Order/Aggregate), + // so the sheet is now eligible. + name: "nested vertical map sheet is eligible (key-hash)", + md: &unittestpb.MallConf{}, + wantOK: true, + wantStrategy: strategyKeyHash, + }, + { + // Top-level vertical map whose value contains a nested HORIZONTAL + // map. Horizontal maps live entirely within one row, so they + // don't even need cross-row aggregation; either partitioning + // strategy would work, but we still pick key-hash because the + // top is a vertical map. + name: "nested horizontal map is eligible (key-hash)", + md: &unittestpb.RewardConf{}, // reward_map (vertical) -> item_map (horizontal) + wantOK: true, + wantStrategy: strategyKeyHash, + }, + { + // HorizontalAggregateMap has aggregate:true on its nested + // horizontal map. Aggregate is a whole-sheet validation that + // would mis-report on per-shard inputs, so this remains + // rejected even after the unique check is loosened. + name: "aggregate field disqualifies", + md: &unittestpb.HorizontalAggregateMap{}, + wantOK: false, + wantSub: "Aggregate", + }, + { + // Top-level vertical list of message is shardable in principle; + // the walk still rejects this particular sheet because its inner + // fields enable Unique. The point of the case is to ensure the + // list branch is actually taken (i.e. not short-circuited by the + // "not a map" check) and that the per-field rejection reason + // flows through. + name: "vertical list with unique sub-field disqualifies", + md: &unittestpb.UniqueFieldInVerticalStructList{}, + wantOK: false, + wantSub: "Unique", + }, + { + // SequenceKeyInVerticalKeyedList is a vertical KEYED list whose + // item id has prop.sequence set. The sequence prop is whole- + // sheet (it asserts the global "key == seq + index"), so we + // reject it. Without the sequence prop, a vertical keyed list + // would now be eligible via key-hash partitioning. + name: "vertical keyed list with sequence prop disqualifies", + md: &unittestpb.SequenceKeyInVerticalKeyedList{}, + wantOK: false, + wantSub: "Sequence", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := newTestSheetParser(t) + if tt.setup != nil { + tt.setup(p) + } + plan := canParallelizeSheet(p, tt.md.ProtoReflect().Descriptor()) + if tt.wantOK { + require.True(t, plan.ok, "want eligible but rejected: %s", plan.reason) + assert.Equal(t, tt.wantStrategy, plan.strategy, "wrong partitioning strategy") + return + } + require.False(t, plan.ok) + assert.Contains(t, plan.reason, tt.wantSub) + }) + } +} + +// makeMallConfRows builds rows for MallConf (vertical map -> vertical map). +// The returned layout is interleaved: shops do NOT appear in contiguous +// blocks. This pins down the property that hash-by-key partitioning preserves +// "same outer-key rows aggregate into one entry" regardless of input ordering. +// +// cols = [ShopID, GoodsID, Price] +// for shop in 1..numShops: +// for goods in 1..goodsPerShop: +// row(shop, goods, shop*1000+goods) +// then shuffle by interleaving shops at every step: +// (s=1,g=1), (s=2,g=1), ..., (s=N,g=1), +// (s=1,g=2), (s=2,g=2), ..., (s=N,g=2), ... +// +// This guarantees that for every shop, its goods rows land in NON-contiguous +// sheet positions, which is precisely the case where row-range sharding would +// break (if we still used it) and where key-hash sharding must succeed. +func makeMallConfRows(numShops, goodsPerShop int) [][]string { + rows := [][]string{ + {"ShopID", "GoodsID", "Price"}, + } + for g := 1; g <= goodsPerShop; g++ { + for s := 1; s <= numShops; s++ { + rows = append(rows, []string{ + fmt.Sprintf("%d", s), + fmt.Sprintf("%d", g), + fmt.Sprintf("%d", s*1000+g), + }) + } + } + return rows +} + +// TestParallelTableParser_NestedVerticalMapEqualsSerial is the headline +// regression test for hash-by-key sharding. It feeds a multi-level vertical- +// map sheet (MallConf: shop_map.vertical -> goods_map.vertical) with an +// interleaved row order so that every ShopID's rows are spread across the +// sheet, then asserts that the parallel result is byte-equal to the serial +// result. +// +// What this test would have caught under the old "row-range" scheme: +// - Splitting the same ShopID's rows across blocks would fire xproto's +// ErrDuplicateKey at the reduce step, so this would have errored out +// instead of returning equal output. +// +// What it pins down for hash-by-key: +// - Same outer-key rows always co-locate in one shard. +// - Inner goods_map aggregation works inside a shard using the unmodified +// serial parseVerticalMapField path. +// - The reduce step's mergeMap finds disjoint outer keys across shards, +// so its duplicate-key check is a defensive no-op. +func TestParallelTableParser_NestedVerticalMapEqualsSerial(t *testing.T) { + // Need enough rows to actually trigger sharding; with goodsPerShop large + // enough that interleaving puts the same shop's rows far apart. + const numShops = 64 + const goodsPerShop = 32 // 64 * 32 = 2048 data rows > minParallelRows + rows := makeMallConfRows(numShops, goodsPerShop) + require.GreaterOrEqual(t, len(rows)-1, minParallelRows, + "test fixture too small to exercise sharding") + sheet := book.NewTableSheet("MallConf", rows) + + serial := &unittestpb.MallConf{} + parseSerial(t, newTestSheetParser(t), serial, sheet) + + parallel := &unittestpb.MallConf{} + parseAuto(t, newTestSheetParser(t), parallel, sheet) + + require.True(t, proto.Equal(serial, parallel), + "parallel result diverges from serial:\nserial=%v\nparallel=%v", serial, parallel) + // Sanity-check the cardinality so a silent "both sides empty" can't + // pass: every ShopID must appear, every shop must have all its goods. + require.Len(t, parallel.GetShopMap(), numShops) + for shopID, shop := range parallel.GetShopMap() { + require.Lenf(t, shop.GetGoodsMap(), goodsPerShop, + "shop %d has wrong goods count", shopID) + } +} + +// TestParallelTableParser_NestedVerticalMapSkewed feeds MallConf with one +// dominant ShopID owning ~all rows, plus a handful of singleton shops. This +// pins down two properties: +// +// 1. Correctness under skew: the dominant shop's goods all land in one +// shard, the singletons hash into other shards (or the same one -- +// either way the result must equal the serial output). +// 2. The "<= 1 active shard" fallback in parseTableInParallel: when every +// row hashes into a single bucket (e.g. only one outer key exists), the +// parallel path must transparently fall back to serial rather than +// spinning up a single-worker goroutine for no benefit. We don't assert +// on the fallback path directly here, but a regression in it would +// surface as a result-mismatch via the proto.Equal check. +func TestParallelTableParser_NestedVerticalMapSkewed(t *testing.T) { + rows := [][]string{ + {"ShopID", "GoodsID", "Price"}, + } + // One dominant shop with minParallelRows worth of goods. + const dominantGoods = minParallelRows + 8 + for g := 1; g <= dominantGoods; g++ { + rows = append(rows, []string{"1", fmt.Sprintf("%d", g), fmt.Sprintf("%d", 1000+g)}) + } + // A handful of singleton shops, sprinkled at the tail. + for s := 2; s <= 5; s++ { + rows = append(rows, []string{fmt.Sprintf("%d", s), "1", fmt.Sprintf("%d", s*1000+1)}) + } + sheet := book.NewTableSheet("MallConf", rows) + + serial := &unittestpb.MallConf{} + parseSerial(t, newTestSheetParser(t), serial, sheet) + + parallel := &unittestpb.MallConf{} + parseAuto(t, newTestSheetParser(t), parallel, sheet) + + require.True(t, proto.Equal(serial, parallel), + "parallel result diverges from serial under skew:\nserial=%v\nparallel=%v", serial, parallel) + require.Len(t, parallel.GetShopMap(), 5) + require.Len(t, parallel.GetShopMap()[1].GetGoodsMap(), dominantGoods) +} + +// TestBucketForKey is a tiny but load-bearing unit test: it pins down the +// invariant "two equal raw key strings always map to the same bucket". A +// regression here (e.g. swapping in a non-deterministic hash) would silently +// fail TestParallelTableParser_NestedVerticalMapEqualsSerial under load, so +// it's worth a dedicated tight check. +func TestBucketForKey(t *testing.T) { + const workers = 8 + for _, key := range []string{"", "1", "42", "ShopABC", "中文键", "1\t"} { + a := bucketForKey(key, workers) + b := bucketForKey(key, workers) + require.Equalf(t, a, b, "bucketForKey(%q) is non-deterministic: %d vs %d", key, a, b) + require.GreaterOrEqualf(t, a, 0, "bucketForKey(%q) negative: %d", key, a) + require.Lessf(t, a, workers, "bucketForKey(%q) out of range: %d", key, a) + } + // Empty key must always go to bucket 0 by policy. + require.Equal(t, 0, bucketForKey("", workers)) +} diff --git a/internal/confgen/parser.go b/internal/confgen/parser.go index 15f5e67b..86ee19b7 100644 --- a/internal/confgen/parser.go +++ b/internal/confgen/parser.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" "sync" + "sync/atomic" + "time" "buf.build/go/protovalidate" "github.com/tableauio/tableau/format" @@ -18,6 +20,7 @@ import ( "github.com/tableauio/tableau/internal/x/xerrors" "github.com/tableauio/tableau/internal/x/xfs" "github.com/tableauio/tableau/internal/x/xproto" + "github.com/tableauio/tableau/log" "github.com/tableauio/tableau/options" "github.com/tableauio/tableau/proto/tableaupb" "google.golang.org/protobuf/encoding/protojson" @@ -68,25 +71,35 @@ func (x *sheetExporter) ScatterAndExport(info *SheetInfo, return filename } // parse main sheet + mainParseBegin := time.Now() mainMsg, err := parseMessageFromOneImporter(info, x.collector, mainImpInfo) if err != nil { return err } + var parseNanos atomic.Int64 + var exportNanos atomic.Int64 + parseNanos.Add(time.Since(mainParseBegin).Nanoseconds()) mainName := getExportedConfName(info, mainImpInfo) + mainExportBegin := time.Now() err = storeMessage(mainMsg, mainName, info.LocationName, x.OutputDir, x.OutputOpt, x.validator) if err != nil { return err } + exportNanos.Add(time.Since(mainExportBegin).Nanoseconds()) g := x.collector.NewGroup(context.Background()) for _, impInfo := range impInfos { // map-reduce: map jobs for concurrent processing g.Go(func(ctx context.Context) error { + parseBegin := time.Now() msg, err := parseMessageFromOneImporter(info, x.collector, impInfo) if err != nil { return err } + parseNanos.Add(time.Since(parseBegin).Nanoseconds()) name := getExportedConfName(info, impInfo) + exportBegin := time.Now() + defer func() { exportNanos.Add(time.Since(exportBegin).Nanoseconds()) }() if info.SheetOpts.Patch == tableaupb.Patch_PATCH_MERGE { if info.ExtInfo.DryRun == options.DryRunPatch { clonedMainMsg := proto.Clone(mainMsg) @@ -101,7 +114,15 @@ func (x *sheetExporter) ScatterAndExport(info *SheetInfo, return storeMessage(msg, name, info.LocationName, x.OutputDir, x.OutputOpt, x.validator) }) } - return g.Wait() + if err := g.Wait(); err != nil { + return err + } + // NOTE: parse/export totals here are SUMS of per-job CPU-style durations + // across all scatter shards (not wall-clock). They reflect work volume, + // not elapsed time, since shards run concurrently. + log.Infof("%15s: %s confgen=%dms export=%dms (scatter, summed across shards)", "perf", info.MD.Name(), + parseNanos.Load()/int64(time.Millisecond), exportNanos.Load()/int64(time.Millisecond)) + return nil } // MergeAndExport parses multiple importer infos and merges them into one @@ -111,10 +132,12 @@ func (x *sheetExporter) MergeAndExport(info *SheetInfo, impInfos ...importer.ImporterInfo) error { // append main allImpInfos := append(impInfos, importer.ImporterInfo{Importer: mainImpInfo}) + parseBegin := time.Now() protomsg, err := ParseMessage(info, x.collector, allImpInfos...) if err != nil { return err } + parseElapsed := time.Since(parseBegin) // exported conf name pattern is : [ParentDir/] getExportedConfName := func(info *SheetInfo, impInfo importer.ImporterInfo) string { // here filename has no ext suffix @@ -126,7 +149,14 @@ func (x *sheetExporter) MergeAndExport(info *SheetInfo, return filename } name := getExportedConfName(info, mainImpInfo) - return storeMessage(protomsg, name, info.LocationName, x.OutputDir, x.OutputOpt, x.validator) + exportBegin := time.Now() + if err := storeMessage(protomsg, name, info.LocationName, x.OutputDir, x.OutputOpt, x.validator); err != nil { + return err + } + exportElapsed := time.Since(exportBegin) + log.Infof("%15s: %s confgen=%dms export=%dms", "perf", info.MD.Name(), + parseElapsed.Milliseconds(), exportElapsed.Milliseconds()) + return nil } type oneMsg struct { diff --git a/internal/confgen/table_parser.go b/internal/confgen/table_parser.go index 8743c561..9f6df18c 100644 --- a/internal/confgen/table_parser.go +++ b/internal/confgen/table_parser.go @@ -11,6 +11,7 @@ import ( "github.com/tableauio/tableau/internal/types" "github.com/tableauio/tableau/internal/x/xerrors" "github.com/tableauio/tableau/internal/x/xproto" + "github.com/tableauio/tableau/log" "github.com/tableauio/tableau/proto/tableaupb" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" @@ -30,6 +31,18 @@ func (p *tableParser) Parse(protomsg proto.Message, sheet *book.Sheet) error { } else { table = sheet.Table } + // Parallel confgen is enabled automatically whenever the sheet is eligible: + // callers no longer opt in via a worksheet/metasheet toggle. The + // eligibility predicate (canParallelizeSheet) is the sole authority -- + // ineligible sheets transparently fall back to the serial path with a + // debug log explaining why, so a regression in eligibility never silently + // degrades correctness, only performance. + md := protomsg.ProtoReflect().Descriptor() + if plan := canParallelizeSheet(p.sheetParser, md); plan.ok { + return p.parseTableInParallel(protomsg, table, plan) + } else if plan.reason != "" { + log.Debugf("parallel confgen disabled for sheet %s: %s", p.sheetOpts.GetName(), plan.reason) + } return p.parse(protomsg, table) } diff --git a/internal/importer/book/tableparser/parser.go b/internal/importer/book/tableparser/parser.go index 543e5d4c..7109f68e 100644 --- a/internal/importer/book/tableparser/parser.go +++ b/internal/importer/book/tableparser/parser.go @@ -11,10 +11,128 @@ func RangeDataRows(table book.Tabler, header *Header, fn func(*book.Row) error) if err != nil { return err } - var prev *book.Row - // [datarow, endRow]: data rows dataRow := table.BeginRow() + header.DataRow - 1 - for row := dataRow; row < table.EndRow(); row++ { + return rangeDataRowsInRange(table, columns, lookupTable, header, dataRow, table.EndRow(), fn) +} + +// RangeDataRowsInBlock ranges data rows in [blockBegin, blockEnd) of the table. +// +// Compared to RangeDataRows, the caller controls the row range, which is useful +// for sharding the data rows across goroutines. The header (name/type rows) is +// still parsed once from the table itself, so the column lookup table is +// consistent across blocks. +// +// NOTE: blockBegin/blockEnd must lie within [dataRow, EndRow()) where dataRow +// is `table.BeginRow() + header.DataRow - 1`. The previous-row chain (used for +// adjacent_key auto-population) restarts at nil at the block boundary, so +// callers MUST ensure the sheet does not rely on cross-block prev linkage +// (e.g. AdjacentKey is not enabled). +func RangeDataRowsInBlock(table book.Tabler, header *Header, blockBegin, blockEnd int, fn func(*book.Row) error) error { + columns, lookupTable, err := parseColumns(table, header) + if err != nil { + return err + } + return rangeDataRowsInRange(table, columns, lookupTable, header, blockBegin, blockEnd, fn) +} + +// DataRowRange returns the [begin, end) data row range of the table according +// to the header settings. It can be used by callers that need to shard the +// data rows for parallel processing. +func DataRowRange(table book.Tabler, header *Header) (int, int) { + return table.BeginRow() + header.DataRow - 1, table.EndRow() +} + +// RangeDataRowsByIndices ranges data rows in the explicit `rowIndices` list of +// the table, in the given order. +// +// Compared to RangeDataRowsInBlock (which iterates a contiguous half-open +// interval), this variant lets callers pick any subset of data rows -- useful +// for hash-partitioned parallel parsing where rows sharing the same outer key +// must be processed by the same goroutine. Callers SHOULD pass `rowIndices` in +// ascending order; the iteration faithfully follows the slice order, so any +// non-ascending input would scramble within-shard row order and break the +// invariant that "the merged result equals the serial result". +// +// The previous-row chain (used for adjacent_key auto-population) restarts at +// nil at every call, so callers MUST ensure the sheet does not rely on +// cross-row prev linkage (e.g. AdjacentKey is not enabled). +func RangeDataRowsByIndices(table book.Tabler, header *Header, rowIndices []int, fn func(*book.Row) error) error { + columns, lookupTable, err := parseColumns(table, header) + if err != nil { + return err + } + return rangeDataRowsAtIndices(table, columns, lookupTable, header, rowIndices, fn) +} + +func rangeDataRowsAtIndices(table book.Tabler, columns map[int]*book.Column, lookupTable book.ColumnLookupTable, header *Header, rowIndices []int, fn func(*book.Row) error) error { + var prev *book.Row + for _, row := range rowIndices { + curr := book.NewRow(row, prev, lookupTable) + for col := table.BeginCol(); col < table.EndCol(); col++ { + data, err := table.Cell(row, col) + if err != nil { + return xerrors.WrapKV(err) + } + curr.AddCell(columns[col], data, header.AdjacentKey) + } + ignored, err := curr.Ignored() + if err != nil { + return err + } + if ignored { + curr.Free() + continue + } + err = fn(curr) + if err != nil { + return err + } + if prev != nil { + prev.Free() + } + prev = curr + } + if prev != nil { + prev.Free() + } + return nil +} + +// ScanColumn reads the raw cell strings of the named column for every data +// row in [beginRow, endRow), returning a slice with one entry per row. It is +// intended for cheap pre-scans that need to bucket rows by a single column +// (e.g. parallel sharders that hash on the top-level outer-key column). +// +// The returned slice is parallel to [beginRow, endRow): index i corresponds +// to row beginRow+i. ScanColumn does not honour `ignored` rows -- the caller +// already accepts that the partition merely needs to be a function of (row, +// key) and that any row-level filtering will happen later, inside the worker. +// +// If the named column is absent, ScanColumn returns an error so the caller +// fails loudly rather than silently bucket all rows into a single shard. +func ScanColumn(table book.Tabler, header *Header, name string, beginRow, endRow int) ([]string, error) { + _, lookupTable, err := parseColumns(table, header) + if err != nil { + return nil, err + } + col, ok := lookupTable[name] + if !ok { + return nil, xerrors.E2014(name) + } + values := make([]string, endRow-beginRow) + for row := beginRow; row < endRow; row++ { + data, cellErr := table.Cell(row, col) + if cellErr != nil { + return nil, xerrors.WrapKV(cellErr) + } + values[row-beginRow] = data + } + return values, nil +} + +func rangeDataRowsInRange(table book.Tabler, columns map[int]*book.Column, lookupTable book.ColumnLookupTable, header *Header, beginRow, endRow int, fn func(*book.Row) error) error { + var prev *book.Row + for row := beginRow; row < endRow; row++ { curr := book.NewRow(row, prev, lookupTable) for col := table.BeginCol(); col < table.EndCol(); col++ { data, err := table.Cell(row, col) diff --git a/internal/x/xpool/sem.go b/internal/x/xpool/sem.go new file mode 100644 index 00000000..b189a0e9 --- /dev/null +++ b/internal/x/xpool/sem.go @@ -0,0 +1,101 @@ +// Package xpool provides lightweight concurrency-limiting primitives shared +// across confgen / protogen pipelines. +// +// The package intentionally exposes a small surface: +// +// - [Semaphore] is a counting semaphore implemented as a buffered channel. +// It is the building block for "at most N goroutines may execute the +// critical section concurrently" patterns. Acquisitions are +// context-cancellable so that errgroup-style fan-out propagates +// cancellation correctly. +// +// Why a custom type instead of golang.org/x/sync/semaphore? The standard +// package supports weighted acquisitions and uses a sync.Mutex + waiter +// list internally; we only ever acquire weight=1, and the chan-based +// implementation is both simpler and faster on the hot path (single +// channel send / receive, zero allocations after construction). +package xpool + +import ( + "context" + "runtime" +) + +// Semaphore is a counting semaphore that bounds the number of goroutines +// allowed to hold a "slot" simultaneously. It is safe for concurrent use +// by multiple goroutines. +// +// The zero value is NOT usable; construct via [NewSemaphore] or +// [NewCPUSemaphore]. +type Semaphore struct { + slots chan struct{} +} + +// NewSemaphore constructs a semaphore with the given capacity. capacity +// must be >= 1; values < 1 are clamped to 1 so that the semaphore always +// admits at least one holder (the alternative -- a deadlocked semaphore -- +// would silently break callers that compute capacity from environment +// signals like GOMAXPROCS in degenerate setups). +func NewSemaphore(capacity int) *Semaphore { + if capacity < 1 { + capacity = 1 + } + return &Semaphore{slots: make(chan struct{}, capacity)} +} + +// NewCPUSemaphore constructs a semaphore sized to runtime.GOMAXPROCS(0), +// the canonical "number of cores Go is allowed to use" signal that is +// cgroup-aware on recent Go releases. This is the right size for CPU- +// bound critical sections. +func NewCPUSemaphore() *Semaphore { + return NewSemaphore(runtime.GOMAXPROCS(0)) +} + +// Cap reports the configured capacity. +func (s *Semaphore) Cap() int { return cap(s.slots) } + +// Len reports the number of slots currently held. Intended for debugging +// / observability; the value is approximate under concurrent mutation. +func (s *Semaphore) Len() int { return len(s.slots) } + +// Acquire blocks until a slot is available or ctx is cancelled. On +// success it returns nil and the caller MUST call [Semaphore.Release] +// exactly once when done. On cancellation it returns ctx.Err() and the +// caller MUST NOT call Release. +func (s *Semaphore) Acquire(ctx context.Context) error { + select { + case s.slots <- struct{}{}: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// TryAcquire attempts to take a slot without blocking. It returns true +// on success (in which case [Semaphore.Release] must be called) or +// false if no slot was immediately available. +func (s *Semaphore) TryAcquire() bool { + select { + case s.slots <- struct{}{}: + return true + default: + return false + } +} + +// Release returns a previously acquired slot. It is the caller's +// responsibility to ensure Release is called at most once per successful +// Acquire / TryAcquire; a stray Release will silently consume a slot +// from another holder, manifesting as a hard-to-diagnose hang. +func (s *Semaphore) Release() { <-s.slots } + +// Run is a convenience helper that acquires a slot, runs fn, then +// releases the slot. If ctx is cancelled before a slot becomes available +// fn is NOT invoked and ctx.Err() is returned. +func (s *Semaphore) Run(ctx context.Context, fn func() error) error { + if err := s.Acquire(ctx); err != nil { + return err + } + defer s.Release() + return fn() +} diff --git a/internal/x/xpool/sem_test.go b/internal/x/xpool/sem_test.go new file mode 100644 index 00000000..000fa876 --- /dev/null +++ b/internal/x/xpool/sem_test.go @@ -0,0 +1,194 @@ +package xpool + +import ( + "context" + "sync" + "sync/atomic" + "testing" + "time" +) + +func TestSemaphore_Cap(t *testing.T) { + s := NewSemaphore(4) + if got, want := s.Cap(), 4; got != want { + t.Fatalf("Cap() = %d, want %d", got, want) + } + if got := s.Len(); got != 0 { + t.Fatalf("Len() of fresh sem = %d, want 0", got) + } +} + +func TestSemaphore_NewClampsBelowOne(t *testing.T) { + for _, n := range []int{0, -1, -100} { + s := NewSemaphore(n) + if s.Cap() != 1 { + t.Fatalf("NewSemaphore(%d).Cap() = %d, want 1 (clamped)", n, s.Cap()) + } + } +} + +func TestSemaphore_AcquireRelease(t *testing.T) { + s := NewSemaphore(2) + ctx := context.Background() + if err := s.Acquire(ctx); err != nil { + t.Fatalf("Acquire 1: %v", err) + } + if err := s.Acquire(ctx); err != nil { + t.Fatalf("Acquire 2: %v", err) + } + if got := s.Len(); got != 2 { + t.Fatalf("Len() = %d, want 2", got) + } + s.Release() + s.Release() + if got := s.Len(); got != 0 { + t.Fatalf("Len() after release = %d, want 0", got) + } +} + +func TestSemaphore_TryAcquire(t *testing.T) { + s := NewSemaphore(1) + if !s.TryAcquire() { + t.Fatal("first TryAcquire failed") + } + if s.TryAcquire() { + t.Fatal("second TryAcquire on full sem must fail") + } + s.Release() + if !s.TryAcquire() { + t.Fatal("TryAcquire after release should succeed") + } + s.Release() +} + +func TestSemaphore_AcquireBlocksThenWakesOnRelease(t *testing.T) { + s := NewSemaphore(1) + if err := s.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire: %v", err) + } + done := make(chan struct{}) + go func() { + _ = s.Acquire(context.Background()) + close(done) + }() + select { + case <-done: + t.Fatal("second Acquire returned before slot was released") + case <-time.After(20 * time.Millisecond): + } + s.Release() + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("second Acquire did not wake up after Release") + } + s.Release() +} + +func TestSemaphore_AcquireCancellation(t *testing.T) { + s := NewSemaphore(1) + if err := s.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire: %v", err) + } + ctx, cancel := context.WithCancel(context.Background()) + errCh := make(chan error, 1) + go func() { errCh <- s.Acquire(ctx) }() + cancel() + select { + case err := <-errCh: + if err == nil { + t.Fatal("Acquire after cancel returned nil error") + } + case <-time.After(time.Second): + t.Fatal("Acquire did not unblock after cancel") + } + // Verify the sem is still usable and not corrupted: capacity unchanged. + if got := s.Len(); got != 1 { + t.Fatalf("Len() = %d, want 1 (cancelled acquire must not consume a slot)", got) + } + s.Release() +} + +// TestSemaphore_BoundsConcurrency stresses the semaphore with many +// goroutines and asserts the in-flight count never exceeds capacity. +func TestSemaphore_BoundsConcurrency(t *testing.T) { + const cap = 4 + const workers = 64 + s := NewSemaphore(cap) + var inFlight int32 + var maxInFlight int32 + var wg sync.WaitGroup + wg.Add(workers) + for i := 0; i < workers; i++ { + go func() { + defer wg.Done() + if err := s.Acquire(context.Background()); err != nil { + t.Errorf("Acquire: %v", err) + return + } + defer s.Release() + cur := atomic.AddInt32(&inFlight, 1) + for { + old := atomic.LoadInt32(&maxInFlight) + if cur <= old || atomic.CompareAndSwapInt32(&maxInFlight, old, cur) { + break + } + } + time.Sleep(time.Millisecond) + atomic.AddInt32(&inFlight, -1) + }() + } + wg.Wait() + if got := atomic.LoadInt32(&maxInFlight); got > cap { + t.Fatalf("max concurrent holders = %d, exceeds cap = %d", got, cap) + } +} + +func TestSemaphore_Run(t *testing.T) { + s := NewSemaphore(1) + called := false + err := s.Run(context.Background(), func() error { + called = true + if got := s.Len(); got != 1 { + t.Errorf("Len inside fn = %d, want 1", got) + } + return nil + }) + if err != nil { + t.Fatalf("Run: %v", err) + } + if !called { + t.Fatal("Run did not invoke fn") + } + if got := s.Len(); got != 0 { + t.Fatalf("Len after Run = %d, want 0", got) + } +} + +func TestSemaphore_RunCancelledBeforeAcquire(t *testing.T) { + s := NewSemaphore(1) + if err := s.Acquire(context.Background()); err != nil { + t.Fatalf("Acquire: %v", err) + } + defer s.Release() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + called := false + err := s.Run(ctx, func() error { + called = true + return nil + }) + if err == nil { + t.Fatal("Run with cancelled ctx returned nil error") + } + if called { + t.Fatal("Run invoked fn even though Acquire failed") + } +} + +func TestNewCPUSemaphore(t *testing.T) { + s := NewCPUSemaphore() + if s.Cap() < 1 { + t.Fatalf("NewCPUSemaphore Cap = %d, want >= 1", s.Cap()) + } +} diff --git a/test/functest/conf/default/ParallelConf.json b/test/functest/conf/default/ParallelConf.json new file mode 100644 index 00000000..eefebd31 --- /dev/null +++ b/test/functest/conf/default/ParallelConf.json @@ -0,0 +1,5124 @@ +{ + "itemMap": { + "1": { + "id": 1, + "name": "item1", + "score": 100 + }, + "2": { + "id": 2, + "name": "item2", + "score": 200 + }, + "3": { + "id": 3, + "name": "item3", + "score": 300 + }, + "4": { + "id": 4, + "name": "item4", + "score": 400 + }, + "5": { + "id": 5, + "name": "item5", + "score": 500 + }, + "6": { + "id": 6, + "name": "item6", + "score": 600 + }, + "7": { + "id": 7, + "name": "item7", + "score": 700 + }, + "8": { + "id": 8, + "name": "item8", + "score": 800 + }, + "9": { + "id": 9, + "name": "item9", + "score": 900 + }, + "10": { + "id": 10, + "name": "item10", + "score": 1000 + }, + "11": { + "id": 11, + "name": "item11", + "score": 1100 + }, + "12": { + "id": 12, + "name": "item12", + "score": 1200 + }, + "13": { + "id": 13, + "name": "item13", + "score": 1300 + }, + "14": { + "id": 14, + "name": "item14", + "score": 1400 + }, + "15": { + "id": 15, + "name": "item15", + "score": 1500 + }, + "16": { + "id": 16, + "name": "item16", + "score": 1600 + }, + "17": { + "id": 17, + "name": "item17", + "score": 1700 + }, + "18": { + "id": 18, + "name": "item18", + "score": 1800 + }, + "19": { + "id": 19, + "name": "item19", + "score": 1900 + }, + "20": { + "id": 20, + "name": "item20", + "score": 2000 + }, + "21": { + "id": 21, + "name": "item21", + "score": 2100 + }, + "22": { + "id": 22, + "name": "item22", + "score": 2200 + }, + "23": { + "id": 23, + "name": "item23", + "score": 2300 + }, + "24": { + "id": 24, + "name": "item24", + "score": 2400 + }, + "25": { + "id": 25, + "name": "item25", + "score": 2500 + }, + "26": { + "id": 26, + "name": "item26", + "score": 2600 + }, + "27": { + "id": 27, + "name": "item27", + "score": 2700 + }, + "28": { + "id": 28, + "name": "item28", + "score": 2800 + }, + "29": { + "id": 29, + "name": "item29", + "score": 2900 + }, + "30": { + "id": 30, + "name": "item30", + "score": 3000 + }, + "31": { + "id": 31, + "name": "item31", + "score": 3100 + }, + "32": { + "id": 32, + "name": "item32", + "score": 3200 + }, + "33": { + "id": 33, + "name": "item33", + "score": 3300 + }, + "34": { + "id": 34, + "name": "item34", + "score": 3400 + }, + "35": { + "id": 35, + "name": "item35", + "score": 3500 + }, + "36": { + "id": 36, + "name": "item36", + "score": 3600 + }, + "37": { + "id": 37, + "name": "item37", + "score": 3700 + }, + "38": { + "id": 38, + "name": "item38", + "score": 3800 + }, + "39": { + "id": 39, + "name": "item39", + "score": 3900 + }, + "40": { + "id": 40, + "name": "item40", + "score": 4000 + }, + "41": { + "id": 41, + "name": "item41", + "score": 4100 + }, + "42": { + "id": 42, + "name": "item42", + "score": 4200 + }, + "43": { + "id": 43, + "name": "item43", + "score": 4300 + }, + "44": { + "id": 44, + "name": "item44", + "score": 4400 + }, + "45": { + "id": 45, + "name": "item45", + "score": 4500 + }, + "46": { + "id": 46, + "name": "item46", + "score": 4600 + }, + "47": { + "id": 47, + "name": "item47", + "score": 4700 + }, + "48": { + "id": 48, + "name": "item48", + "score": 4800 + }, + "49": { + "id": 49, + "name": "item49", + "score": 4900 + }, + "50": { + "id": 50, + "name": "item50", + "score": 5000 + }, + "51": { + "id": 51, + "name": "item51", + "score": 5100 + }, + "52": { + "id": 52, + "name": "item52", + "score": 5200 + }, + "53": { + "id": 53, + "name": "item53", + "score": 5300 + }, + "54": { + "id": 54, + "name": "item54", + "score": 5400 + }, + "55": { + "id": 55, + "name": "item55", + "score": 5500 + }, + "56": { + "id": 56, + "name": "item56", + "score": 5600 + }, + "57": { + "id": 57, + "name": "item57", + "score": 5700 + }, + "58": { + "id": 58, + "name": "item58", + "score": 5800 + }, + "59": { + "id": 59, + "name": "item59", + "score": 5900 + }, + "60": { + "id": 60, + "name": "item60", + "score": 6000 + }, + "61": { + "id": 61, + "name": "item61", + "score": 6100 + }, + "62": { + "id": 62, + "name": "item62", + "score": 6200 + }, + "63": { + "id": 63, + "name": "item63", + "score": 6300 + }, + "64": { + "id": 64, + "name": "item64", + "score": 6400 + }, + "65": { + "id": 65, + "name": "item65", + "score": 6500 + }, + "66": { + "id": 66, + "name": "item66", + "score": 6600 + }, + "67": { + "id": 67, + "name": "item67", + "score": 6700 + }, + "68": { + "id": 68, + "name": "item68", + "score": 6800 + }, + "69": { + "id": 69, + "name": "item69", + "score": 6900 + }, + "70": { + "id": 70, + "name": "item70", + "score": 7000 + }, + "71": { + "id": 71, + "name": "item71", + "score": 7100 + }, + "72": { + "id": 72, + "name": "item72", + "score": 7200 + }, + "73": { + "id": 73, + "name": "item73", + "score": 7300 + }, + "74": { + "id": 74, + "name": "item74", + "score": 7400 + }, + "75": { + "id": 75, + "name": "item75", + "score": 7500 + }, + "76": { + "id": 76, + "name": "item76", + "score": 7600 + }, + "77": { + "id": 77, + "name": "item77", + "score": 7700 + }, + "78": { + "id": 78, + "name": "item78", + "score": 7800 + }, + "79": { + "id": 79, + "name": "item79", + "score": 7900 + }, + "80": { + "id": 80, + "name": "item80", + "score": 8000 + }, + "81": { + "id": 81, + "name": "item81", + "score": 8100 + }, + "82": { + "id": 82, + "name": "item82", + "score": 8200 + }, + "83": { + "id": 83, + "name": "item83", + "score": 8300 + }, + "84": { + "id": 84, + "name": "item84", + "score": 8400 + }, + "85": { + "id": 85, + "name": "item85", + "score": 8500 + }, + "86": { + "id": 86, + "name": "item86", + "score": 8600 + }, + "87": { + "id": 87, + "name": "item87", + "score": 8700 + }, + "88": { + "id": 88, + "name": "item88", + "score": 8800 + }, + "89": { + "id": 89, + "name": "item89", + "score": 8900 + }, + "90": { + "id": 90, + "name": "item90", + "score": 9000 + }, + "91": { + "id": 91, + "name": "item91", + "score": 9100 + }, + "92": { + "id": 92, + "name": "item92", + "score": 9200 + }, + "93": { + "id": 93, + "name": "item93", + "score": 9300 + }, + "94": { + "id": 94, + "name": "item94", + "score": 9400 + }, + "95": { + "id": 95, + "name": "item95", + "score": 9500 + }, + "96": { + "id": 96, + "name": "item96", + "score": 9600 + }, + "97": { + "id": 97, + "name": "item97", + "score": 9700 + }, + "98": { + "id": 98, + "name": "item98", + "score": 9800 + }, + "99": { + "id": 99, + "name": "item99", + "score": 9900 + }, + "100": { + "id": 100, + "name": "item100", + "score": 10000 + }, + "101": { + "id": 101, + "name": "item101", + "score": 10100 + }, + "102": { + "id": 102, + "name": "item102", + "score": 10200 + }, + "103": { + "id": 103, + "name": "item103", + "score": 10300 + }, + "104": { + "id": 104, + "name": "item104", + "score": 10400 + }, + "105": { + "id": 105, + "name": "item105", + "score": 10500 + }, + "106": { + "id": 106, + "name": "item106", + "score": 10600 + }, + "107": { + "id": 107, + "name": "item107", + "score": 10700 + }, + "108": { + "id": 108, + "name": "item108", + "score": 10800 + }, + "109": { + "id": 109, + "name": "item109", + "score": 10900 + }, + "110": { + "id": 110, + "name": "item110", + "score": 11000 + }, + "111": { + "id": 111, + "name": "item111", + "score": 11100 + }, + "112": { + "id": 112, + "name": "item112", + "score": 11200 + }, + "113": { + "id": 113, + "name": "item113", + "score": 11300 + }, + "114": { + "id": 114, + "name": "item114", + "score": 11400 + }, + "115": { + "id": 115, + "name": "item115", + "score": 11500 + }, + "116": { + "id": 116, + "name": "item116", + "score": 11600 + }, + "117": { + "id": 117, + "name": "item117", + "score": 11700 + }, + "118": { + "id": 118, + "name": "item118", + "score": 11800 + }, + "119": { + "id": 119, + "name": "item119", + "score": 11900 + }, + "120": { + "id": 120, + "name": "item120", + "score": 12000 + }, + "121": { + "id": 121, + "name": "item121", + "score": 12100 + }, + "122": { + "id": 122, + "name": "item122", + "score": 12200 + }, + "123": { + "id": 123, + "name": "item123", + "score": 12300 + }, + "124": { + "id": 124, + "name": "item124", + "score": 12400 + }, + "125": { + "id": 125, + "name": "item125", + "score": 12500 + }, + "126": { + "id": 126, + "name": "item126", + "score": 12600 + }, + "127": { + "id": 127, + "name": "item127", + "score": 12700 + }, + "128": { + "id": 128, + "name": "item128", + "score": 12800 + }, + "129": { + "id": 129, + "name": "item129", + "score": 12900 + }, + "130": { + "id": 130, + "name": "item130", + "score": 13000 + }, + "131": { + "id": 131, + "name": "item131", + "score": 13100 + }, + "132": { + "id": 132, + "name": "item132", + "score": 13200 + }, + "133": { + "id": 133, + "name": "item133", + "score": 13300 + }, + "134": { + "id": 134, + "name": "item134", + "score": 13400 + }, + "135": { + "id": 135, + "name": "item135", + "score": 13500 + }, + "136": { + "id": 136, + "name": "item136", + "score": 13600 + }, + "137": { + "id": 137, + "name": "item137", + "score": 13700 + }, + "138": { + "id": 138, + "name": "item138", + "score": 13800 + }, + "139": { + "id": 139, + "name": "item139", + "score": 13900 + }, + "140": { + "id": 140, + "name": "item140", + "score": 14000 + }, + "141": { + "id": 141, + "name": "item141", + "score": 14100 + }, + "142": { + "id": 142, + "name": "item142", + "score": 14200 + }, + "143": { + "id": 143, + "name": "item143", + "score": 14300 + }, + "144": { + "id": 144, + "name": "item144", + "score": 14400 + }, + "145": { + "id": 145, + "name": "item145", + "score": 14500 + }, + "146": { + "id": 146, + "name": "item146", + "score": 14600 + }, + "147": { + "id": 147, + "name": "item147", + "score": 14700 + }, + "148": { + "id": 148, + "name": "item148", + "score": 14800 + }, + "149": { + "id": 149, + "name": "item149", + "score": 14900 + }, + "150": { + "id": 150, + "name": "item150", + "score": 15000 + }, + "151": { + "id": 151, + "name": "item151", + "score": 15100 + }, + "152": { + "id": 152, + "name": "item152", + "score": 15200 + }, + "153": { + "id": 153, + "name": "item153", + "score": 15300 + }, + "154": { + "id": 154, + "name": "item154", + "score": 15400 + }, + "155": { + "id": 155, + "name": "item155", + "score": 15500 + }, + "156": { + "id": 156, + "name": "item156", + "score": 15600 + }, + "157": { + "id": 157, + "name": "item157", + "score": 15700 + }, + "158": { + "id": 158, + "name": "item158", + "score": 15800 + }, + "159": { + "id": 159, + "name": "item159", + "score": 15900 + }, + "160": { + "id": 160, + "name": "item160", + "score": 16000 + }, + "161": { + "id": 161, + "name": "item161", + "score": 16100 + }, + "162": { + "id": 162, + "name": "item162", + "score": 16200 + }, + "163": { + "id": 163, + "name": "item163", + "score": 16300 + }, + "164": { + "id": 164, + "name": "item164", + "score": 16400 + }, + "165": { + "id": 165, + "name": "item165", + "score": 16500 + }, + "166": { + "id": 166, + "name": "item166", + "score": 16600 + }, + "167": { + "id": 167, + "name": "item167", + "score": 16700 + }, + "168": { + "id": 168, + "name": "item168", + "score": 16800 + }, + "169": { + "id": 169, + "name": "item169", + "score": 16900 + }, + "170": { + "id": 170, + "name": "item170", + "score": 17000 + }, + "171": { + "id": 171, + "name": "item171", + "score": 17100 + }, + "172": { + "id": 172, + "name": "item172", + "score": 17200 + }, + "173": { + "id": 173, + "name": "item173", + "score": 17300 + }, + "174": { + "id": 174, + "name": "item174", + "score": 17400 + }, + "175": { + "id": 175, + "name": "item175", + "score": 17500 + }, + "176": { + "id": 176, + "name": "item176", + "score": 17600 + }, + "177": { + "id": 177, + "name": "item177", + "score": 17700 + }, + "178": { + "id": 178, + "name": "item178", + "score": 17800 + }, + "179": { + "id": 179, + "name": "item179", + "score": 17900 + }, + "180": { + "id": 180, + "name": "item180", + "score": 18000 + }, + "181": { + "id": 181, + "name": "item181", + "score": 18100 + }, + "182": { + "id": 182, + "name": "item182", + "score": 18200 + }, + "183": { + "id": 183, + "name": "item183", + "score": 18300 + }, + "184": { + "id": 184, + "name": "item184", + "score": 18400 + }, + "185": { + "id": 185, + "name": "item185", + "score": 18500 + }, + "186": { + "id": 186, + "name": "item186", + "score": 18600 + }, + "187": { + "id": 187, + "name": "item187", + "score": 18700 + }, + "188": { + "id": 188, + "name": "item188", + "score": 18800 + }, + "189": { + "id": 189, + "name": "item189", + "score": 18900 + }, + "190": { + "id": 190, + "name": "item190", + "score": 19000 + }, + "191": { + "id": 191, + "name": "item191", + "score": 19100 + }, + "192": { + "id": 192, + "name": "item192", + "score": 19200 + }, + "193": { + "id": 193, + "name": "item193", + "score": 19300 + }, + "194": { + "id": 194, + "name": "item194", + "score": 19400 + }, + "195": { + "id": 195, + "name": "item195", + "score": 19500 + }, + "196": { + "id": 196, + "name": "item196", + "score": 19600 + }, + "197": { + "id": 197, + "name": "item197", + "score": 19700 + }, + "198": { + "id": 198, + "name": "item198", + "score": 19800 + }, + "199": { + "id": 199, + "name": "item199", + "score": 19900 + }, + "200": { + "id": 200, + "name": "item200", + "score": 20000 + }, + "201": { + "id": 201, + "name": "item201", + "score": 20100 + }, + "202": { + "id": 202, + "name": "item202", + "score": 20200 + }, + "203": { + "id": 203, + "name": "item203", + "score": 20300 + }, + "204": { + "id": 204, + "name": "item204", + "score": 20400 + }, + "205": { + "id": 205, + "name": "item205", + "score": 20500 + }, + "206": { + "id": 206, + "name": "item206", + "score": 20600 + }, + "207": { + "id": 207, + "name": "item207", + "score": 20700 + }, + "208": { + "id": 208, + "name": "item208", + "score": 20800 + }, + "209": { + "id": 209, + "name": "item209", + "score": 20900 + }, + "210": { + "id": 210, + "name": "item210", + "score": 21000 + }, + "211": { + "id": 211, + "name": "item211", + "score": 21100 + }, + "212": { + "id": 212, + "name": "item212", + "score": 21200 + }, + "213": { + "id": 213, + "name": "item213", + "score": 21300 + }, + "214": { + "id": 214, + "name": "item214", + "score": 21400 + }, + "215": { + "id": 215, + "name": "item215", + "score": 21500 + }, + "216": { + "id": 216, + "name": "item216", + "score": 21600 + }, + "217": { + "id": 217, + "name": "item217", + "score": 21700 + }, + "218": { + "id": 218, + "name": "item218", + "score": 21800 + }, + "219": { + "id": 219, + "name": "item219", + "score": 21900 + }, + "220": { + "id": 220, + "name": "item220", + "score": 22000 + }, + "221": { + "id": 221, + "name": "item221", + "score": 22100 + }, + "222": { + "id": 222, + "name": "item222", + "score": 22200 + }, + "223": { + "id": 223, + "name": "item223", + "score": 22300 + }, + "224": { + "id": 224, + "name": "item224", + "score": 22400 + }, + "225": { + "id": 225, + "name": "item225", + "score": 22500 + }, + "226": { + "id": 226, + "name": "item226", + "score": 22600 + }, + "227": { + "id": 227, + "name": "item227", + "score": 22700 + }, + "228": { + "id": 228, + "name": "item228", + "score": 22800 + }, + "229": { + "id": 229, + "name": "item229", + "score": 22900 + }, + "230": { + "id": 230, + "name": "item230", + "score": 23000 + }, + "231": { + "id": 231, + "name": "item231", + "score": 23100 + }, + "232": { + "id": 232, + "name": "item232", + "score": 23200 + }, + "233": { + "id": 233, + "name": "item233", + "score": 23300 + }, + "234": { + "id": 234, + "name": "item234", + "score": 23400 + }, + "235": { + "id": 235, + "name": "item235", + "score": 23500 + }, + "236": { + "id": 236, + "name": "item236", + "score": 23600 + }, + "237": { + "id": 237, + "name": "item237", + "score": 23700 + }, + "238": { + "id": 238, + "name": "item238", + "score": 23800 + }, + "239": { + "id": 239, + "name": "item239", + "score": 23900 + }, + "240": { + "id": 240, + "name": "item240", + "score": 24000 + }, + "241": { + "id": 241, + "name": "item241", + "score": 24100 + }, + "242": { + "id": 242, + "name": "item242", + "score": 24200 + }, + "243": { + "id": 243, + "name": "item243", + "score": 24300 + }, + "244": { + "id": 244, + "name": "item244", + "score": 24400 + }, + "245": { + "id": 245, + "name": "item245", + "score": 24500 + }, + "246": { + "id": 246, + "name": "item246", + "score": 24600 + }, + "247": { + "id": 247, + "name": "item247", + "score": 24700 + }, + "248": { + "id": 248, + "name": "item248", + "score": 24800 + }, + "249": { + "id": 249, + "name": "item249", + "score": 24900 + }, + "250": { + "id": 250, + "name": "item250", + "score": 25000 + }, + "251": { + "id": 251, + "name": "item251", + "score": 25100 + }, + "252": { + "id": 252, + "name": "item252", + "score": 25200 + }, + "253": { + "id": 253, + "name": "item253", + "score": 25300 + }, + "254": { + "id": 254, + "name": "item254", + "score": 25400 + }, + "255": { + "id": 255, + "name": "item255", + "score": 25500 + }, + "256": { + "id": 256, + "name": "item256", + "score": 25600 + }, + "257": { + "id": 257, + "name": "item257", + "score": 25700 + }, + "258": { + "id": 258, + "name": "item258", + "score": 25800 + }, + "259": { + "id": 259, + "name": "item259", + "score": 25900 + }, + "260": { + "id": 260, + "name": "item260", + "score": 26000 + }, + "261": { + "id": 261, + "name": "item261", + "score": 26100 + }, + "262": { + "id": 262, + "name": "item262", + "score": 26200 + }, + "263": { + "id": 263, + "name": "item263", + "score": 26300 + }, + "264": { + "id": 264, + "name": "item264", + "score": 26400 + }, + "265": { + "id": 265, + "name": "item265", + "score": 26500 + }, + "266": { + "id": 266, + "name": "item266", + "score": 26600 + }, + "267": { + "id": 267, + "name": "item267", + "score": 26700 + }, + "268": { + "id": 268, + "name": "item268", + "score": 26800 + }, + "269": { + "id": 269, + "name": "item269", + "score": 26900 + }, + "270": { + "id": 270, + "name": "item270", + "score": 27000 + }, + "271": { + "id": 271, + "name": "item271", + "score": 27100 + }, + "272": { + "id": 272, + "name": "item272", + "score": 27200 + }, + "273": { + "id": 273, + "name": "item273", + "score": 27300 + }, + "274": { + "id": 274, + "name": "item274", + "score": 27400 + }, + "275": { + "id": 275, + "name": "item275", + "score": 27500 + }, + "276": { + "id": 276, + "name": "item276", + "score": 27600 + }, + "277": { + "id": 277, + "name": "item277", + "score": 27700 + }, + "278": { + "id": 278, + "name": "item278", + "score": 27800 + }, + "279": { + "id": 279, + "name": "item279", + "score": 27900 + }, + "280": { + "id": 280, + "name": "item280", + "score": 28000 + }, + "281": { + "id": 281, + "name": "item281", + "score": 28100 + }, + "282": { + "id": 282, + "name": "item282", + "score": 28200 + }, + "283": { + "id": 283, + "name": "item283", + "score": 28300 + }, + "284": { + "id": 284, + "name": "item284", + "score": 28400 + }, + "285": { + "id": 285, + "name": "item285", + "score": 28500 + }, + "286": { + "id": 286, + "name": "item286", + "score": 28600 + }, + "287": { + "id": 287, + "name": "item287", + "score": 28700 + }, + "288": { + "id": 288, + "name": "item288", + "score": 28800 + }, + "289": { + "id": 289, + "name": "item289", + "score": 28900 + }, + "290": { + "id": 290, + "name": "item290", + "score": 29000 + }, + "291": { + "id": 291, + "name": "item291", + "score": 29100 + }, + "292": { + "id": 292, + "name": "item292", + "score": 29200 + }, + "293": { + "id": 293, + "name": "item293", + "score": 29300 + }, + "294": { + "id": 294, + "name": "item294", + "score": 29400 + }, + "295": { + "id": 295, + "name": "item295", + "score": 29500 + }, + "296": { + "id": 296, + "name": "item296", + "score": 29600 + }, + "297": { + "id": 297, + "name": "item297", + "score": 29700 + }, + "298": { + "id": 298, + "name": "item298", + "score": 29800 + }, + "299": { + "id": 299, + "name": "item299", + "score": 29900 + }, + "300": { + "id": 300, + "name": "item300", + "score": 30000 + }, + "301": { + "id": 301, + "name": "item301", + "score": 30100 + }, + "302": { + "id": 302, + "name": "item302", + "score": 30200 + }, + "303": { + "id": 303, + "name": "item303", + "score": 30300 + }, + "304": { + "id": 304, + "name": "item304", + "score": 30400 + }, + "305": { + "id": 305, + "name": "item305", + "score": 30500 + }, + "306": { + "id": 306, + "name": "item306", + "score": 30600 + }, + "307": { + "id": 307, + "name": "item307", + "score": 30700 + }, + "308": { + "id": 308, + "name": "item308", + "score": 30800 + }, + "309": { + "id": 309, + "name": "item309", + "score": 30900 + }, + "310": { + "id": 310, + "name": "item310", + "score": 31000 + }, + "311": { + "id": 311, + "name": "item311", + "score": 31100 + }, + "312": { + "id": 312, + "name": "item312", + "score": 31200 + }, + "313": { + "id": 313, + "name": "item313", + "score": 31300 + }, + "314": { + "id": 314, + "name": "item314", + "score": 31400 + }, + "315": { + "id": 315, + "name": "item315", + "score": 31500 + }, + "316": { + "id": 316, + "name": "item316", + "score": 31600 + }, + "317": { + "id": 317, + "name": "item317", + "score": 31700 + }, + "318": { + "id": 318, + "name": "item318", + "score": 31800 + }, + "319": { + "id": 319, + "name": "item319", + "score": 31900 + }, + "320": { + "id": 320, + "name": "item320", + "score": 32000 + }, + "321": { + "id": 321, + "name": "item321", + "score": 32100 + }, + "322": { + "id": 322, + "name": "item322", + "score": 32200 + }, + "323": { + "id": 323, + "name": "item323", + "score": 32300 + }, + "324": { + "id": 324, + "name": "item324", + "score": 32400 + }, + "325": { + "id": 325, + "name": "item325", + "score": 32500 + }, + "326": { + "id": 326, + "name": "item326", + "score": 32600 + }, + "327": { + "id": 327, + "name": "item327", + "score": 32700 + }, + "328": { + "id": 328, + "name": "item328", + "score": 32800 + }, + "329": { + "id": 329, + "name": "item329", + "score": 32900 + }, + "330": { + "id": 330, + "name": "item330", + "score": 33000 + }, + "331": { + "id": 331, + "name": "item331", + "score": 33100 + }, + "332": { + "id": 332, + "name": "item332", + "score": 33200 + }, + "333": { + "id": 333, + "name": "item333", + "score": 33300 + }, + "334": { + "id": 334, + "name": "item334", + "score": 33400 + }, + "335": { + "id": 335, + "name": "item335", + "score": 33500 + }, + "336": { + "id": 336, + "name": "item336", + "score": 33600 + }, + "337": { + "id": 337, + "name": "item337", + "score": 33700 + }, + "338": { + "id": 338, + "name": "item338", + "score": 33800 + }, + "339": { + "id": 339, + "name": "item339", + "score": 33900 + }, + "340": { + "id": 340, + "name": "item340", + "score": 34000 + }, + "341": { + "id": 341, + "name": "item341", + "score": 34100 + }, + "342": { + "id": 342, + "name": "item342", + "score": 34200 + }, + "343": { + "id": 343, + "name": "item343", + "score": 34300 + }, + "344": { + "id": 344, + "name": "item344", + "score": 34400 + }, + "345": { + "id": 345, + "name": "item345", + "score": 34500 + }, + "346": { + "id": 346, + "name": "item346", + "score": 34600 + }, + "347": { + "id": 347, + "name": "item347", + "score": 34700 + }, + "348": { + "id": 348, + "name": "item348", + "score": 34800 + }, + "349": { + "id": 349, + "name": "item349", + "score": 34900 + }, + "350": { + "id": 350, + "name": "item350", + "score": 35000 + }, + "351": { + "id": 351, + "name": "item351", + "score": 35100 + }, + "352": { + "id": 352, + "name": "item352", + "score": 35200 + }, + "353": { + "id": 353, + "name": "item353", + "score": 35300 + }, + "354": { + "id": 354, + "name": "item354", + "score": 35400 + }, + "355": { + "id": 355, + "name": "item355", + "score": 35500 + }, + "356": { + "id": 356, + "name": "item356", + "score": 35600 + }, + "357": { + "id": 357, + "name": "item357", + "score": 35700 + }, + "358": { + "id": 358, + "name": "item358", + "score": 35800 + }, + "359": { + "id": 359, + "name": "item359", + "score": 35900 + }, + "360": { + "id": 360, + "name": "item360", + "score": 36000 + }, + "361": { + "id": 361, + "name": "item361", + "score": 36100 + }, + "362": { + "id": 362, + "name": "item362", + "score": 36200 + }, + "363": { + "id": 363, + "name": "item363", + "score": 36300 + }, + "364": { + "id": 364, + "name": "item364", + "score": 36400 + }, + "365": { + "id": 365, + "name": "item365", + "score": 36500 + }, + "366": { + "id": 366, + "name": "item366", + "score": 36600 + }, + "367": { + "id": 367, + "name": "item367", + "score": 36700 + }, + "368": { + "id": 368, + "name": "item368", + "score": 36800 + }, + "369": { + "id": 369, + "name": "item369", + "score": 36900 + }, + "370": { + "id": 370, + "name": "item370", + "score": 37000 + }, + "371": { + "id": 371, + "name": "item371", + "score": 37100 + }, + "372": { + "id": 372, + "name": "item372", + "score": 37200 + }, + "373": { + "id": 373, + "name": "item373", + "score": 37300 + }, + "374": { + "id": 374, + "name": "item374", + "score": 37400 + }, + "375": { + "id": 375, + "name": "item375", + "score": 37500 + }, + "376": { + "id": 376, + "name": "item376", + "score": 37600 + }, + "377": { + "id": 377, + "name": "item377", + "score": 37700 + }, + "378": { + "id": 378, + "name": "item378", + "score": 37800 + }, + "379": { + "id": 379, + "name": "item379", + "score": 37900 + }, + "380": { + "id": 380, + "name": "item380", + "score": 38000 + }, + "381": { + "id": 381, + "name": "item381", + "score": 38100 + }, + "382": { + "id": 382, + "name": "item382", + "score": 38200 + }, + "383": { + "id": 383, + "name": "item383", + "score": 38300 + }, + "384": { + "id": 384, + "name": "item384", + "score": 38400 + }, + "385": { + "id": 385, + "name": "item385", + "score": 38500 + }, + "386": { + "id": 386, + "name": "item386", + "score": 38600 + }, + "387": { + "id": 387, + "name": "item387", + "score": 38700 + }, + "388": { + "id": 388, + "name": "item388", + "score": 38800 + }, + "389": { + "id": 389, + "name": "item389", + "score": 38900 + }, + "390": { + "id": 390, + "name": "item390", + "score": 39000 + }, + "391": { + "id": 391, + "name": "item391", + "score": 39100 + }, + "392": { + "id": 392, + "name": "item392", + "score": 39200 + }, + "393": { + "id": 393, + "name": "item393", + "score": 39300 + }, + "394": { + "id": 394, + "name": "item394", + "score": 39400 + }, + "395": { + "id": 395, + "name": "item395", + "score": 39500 + }, + "396": { + "id": 396, + "name": "item396", + "score": 39600 + }, + "397": { + "id": 397, + "name": "item397", + "score": 39700 + }, + "398": { + "id": 398, + "name": "item398", + "score": 39800 + }, + "399": { + "id": 399, + "name": "item399", + "score": 39900 + }, + "400": { + "id": 400, + "name": "item400", + "score": 40000 + }, + "401": { + "id": 401, + "name": "item401", + "score": 40100 + }, + "402": { + "id": 402, + "name": "item402", + "score": 40200 + }, + "403": { + "id": 403, + "name": "item403", + "score": 40300 + }, + "404": { + "id": 404, + "name": "item404", + "score": 40400 + }, + "405": { + "id": 405, + "name": "item405", + "score": 40500 + }, + "406": { + "id": 406, + "name": "item406", + "score": 40600 + }, + "407": { + "id": 407, + "name": "item407", + "score": 40700 + }, + "408": { + "id": 408, + "name": "item408", + "score": 40800 + }, + "409": { + "id": 409, + "name": "item409", + "score": 40900 + }, + "410": { + "id": 410, + "name": "item410", + "score": 41000 + }, + "411": { + "id": 411, + "name": "item411", + "score": 41100 + }, + "412": { + "id": 412, + "name": "item412", + "score": 41200 + }, + "413": { + "id": 413, + "name": "item413", + "score": 41300 + }, + "414": { + "id": 414, + "name": "item414", + "score": 41400 + }, + "415": { + "id": 415, + "name": "item415", + "score": 41500 + }, + "416": { + "id": 416, + "name": "item416", + "score": 41600 + }, + "417": { + "id": 417, + "name": "item417", + "score": 41700 + }, + "418": { + "id": 418, + "name": "item418", + "score": 41800 + }, + "419": { + "id": 419, + "name": "item419", + "score": 41900 + }, + "420": { + "id": 420, + "name": "item420", + "score": 42000 + }, + "421": { + "id": 421, + "name": "item421", + "score": 42100 + }, + "422": { + "id": 422, + "name": "item422", + "score": 42200 + }, + "423": { + "id": 423, + "name": "item423", + "score": 42300 + }, + "424": { + "id": 424, + "name": "item424", + "score": 42400 + }, + "425": { + "id": 425, + "name": "item425", + "score": 42500 + }, + "426": { + "id": 426, + "name": "item426", + "score": 42600 + }, + "427": { + "id": 427, + "name": "item427", + "score": 42700 + }, + "428": { + "id": 428, + "name": "item428", + "score": 42800 + }, + "429": { + "id": 429, + "name": "item429", + "score": 42900 + }, + "430": { + "id": 430, + "name": "item430", + "score": 43000 + }, + "431": { + "id": 431, + "name": "item431", + "score": 43100 + }, + "432": { + "id": 432, + "name": "item432", + "score": 43200 + }, + "433": { + "id": 433, + "name": "item433", + "score": 43300 + }, + "434": { + "id": 434, + "name": "item434", + "score": 43400 + }, + "435": { + "id": 435, + "name": "item435", + "score": 43500 + }, + "436": { + "id": 436, + "name": "item436", + "score": 43600 + }, + "437": { + "id": 437, + "name": "item437", + "score": 43700 + }, + "438": { + "id": 438, + "name": "item438", + "score": 43800 + }, + "439": { + "id": 439, + "name": "item439", + "score": 43900 + }, + "440": { + "id": 440, + "name": "item440", + "score": 44000 + }, + "441": { + "id": 441, + "name": "item441", + "score": 44100 + }, + "442": { + "id": 442, + "name": "item442", + "score": 44200 + }, + "443": { + "id": 443, + "name": "item443", + "score": 44300 + }, + "444": { + "id": 444, + "name": "item444", + "score": 44400 + }, + "445": { + "id": 445, + "name": "item445", + "score": 44500 + }, + "446": { + "id": 446, + "name": "item446", + "score": 44600 + }, + "447": { + "id": 447, + "name": "item447", + "score": 44700 + }, + "448": { + "id": 448, + "name": "item448", + "score": 44800 + }, + "449": { + "id": 449, + "name": "item449", + "score": 44900 + }, + "450": { + "id": 450, + "name": "item450", + "score": 45000 + }, + "451": { + "id": 451, + "name": "item451", + "score": 45100 + }, + "452": { + "id": 452, + "name": "item452", + "score": 45200 + }, + "453": { + "id": 453, + "name": "item453", + "score": 45300 + }, + "454": { + "id": 454, + "name": "item454", + "score": 45400 + }, + "455": { + "id": 455, + "name": "item455", + "score": 45500 + }, + "456": { + "id": 456, + "name": "item456", + "score": 45600 + }, + "457": { + "id": 457, + "name": "item457", + "score": 45700 + }, + "458": { + "id": 458, + "name": "item458", + "score": 45800 + }, + "459": { + "id": 459, + "name": "item459", + "score": 45900 + }, + "460": { + "id": 460, + "name": "item460", + "score": 46000 + }, + "461": { + "id": 461, + "name": "item461", + "score": 46100 + }, + "462": { + "id": 462, + "name": "item462", + "score": 46200 + }, + "463": { + "id": 463, + "name": "item463", + "score": 46300 + }, + "464": { + "id": 464, + "name": "item464", + "score": 46400 + }, + "465": { + "id": 465, + "name": "item465", + "score": 46500 + }, + "466": { + "id": 466, + "name": "item466", + "score": 46600 + }, + "467": { + "id": 467, + "name": "item467", + "score": 46700 + }, + "468": { + "id": 468, + "name": "item468", + "score": 46800 + }, + "469": { + "id": 469, + "name": "item469", + "score": 46900 + }, + "470": { + "id": 470, + "name": "item470", + "score": 47000 + }, + "471": { + "id": 471, + "name": "item471", + "score": 47100 + }, + "472": { + "id": 472, + "name": "item472", + "score": 47200 + }, + "473": { + "id": 473, + "name": "item473", + "score": 47300 + }, + "474": { + "id": 474, + "name": "item474", + "score": 47400 + }, + "475": { + "id": 475, + "name": "item475", + "score": 47500 + }, + "476": { + "id": 476, + "name": "item476", + "score": 47600 + }, + "477": { + "id": 477, + "name": "item477", + "score": 47700 + }, + "478": { + "id": 478, + "name": "item478", + "score": 47800 + }, + "479": { + "id": 479, + "name": "item479", + "score": 47900 + }, + "480": { + "id": 480, + "name": "item480", + "score": 48000 + }, + "481": { + "id": 481, + "name": "item481", + "score": 48100 + }, + "482": { + "id": 482, + "name": "item482", + "score": 48200 + }, + "483": { + "id": 483, + "name": "item483", + "score": 48300 + }, + "484": { + "id": 484, + "name": "item484", + "score": 48400 + }, + "485": { + "id": 485, + "name": "item485", + "score": 48500 + }, + "486": { + "id": 486, + "name": "item486", + "score": 48600 + }, + "487": { + "id": 487, + "name": "item487", + "score": 48700 + }, + "488": { + "id": 488, + "name": "item488", + "score": 48800 + }, + "489": { + "id": 489, + "name": "item489", + "score": 48900 + }, + "490": { + "id": 490, + "name": "item490", + "score": 49000 + }, + "491": { + "id": 491, + "name": "item491", + "score": 49100 + }, + "492": { + "id": 492, + "name": "item492", + "score": 49200 + }, + "493": { + "id": 493, + "name": "item493", + "score": 49300 + }, + "494": { + "id": 494, + "name": "item494", + "score": 49400 + }, + "495": { + "id": 495, + "name": "item495", + "score": 49500 + }, + "496": { + "id": 496, + "name": "item496", + "score": 49600 + }, + "497": { + "id": 497, + "name": "item497", + "score": 49700 + }, + "498": { + "id": 498, + "name": "item498", + "score": 49800 + }, + "499": { + "id": 499, + "name": "item499", + "score": 49900 + }, + "500": { + "id": 500, + "name": "item500", + "score": 50000 + }, + "501": { + "id": 501, + "name": "item501", + "score": 50100 + }, + "502": { + "id": 502, + "name": "item502", + "score": 50200 + }, + "503": { + "id": 503, + "name": "item503", + "score": 50300 + }, + "504": { + "id": 504, + "name": "item504", + "score": 50400 + }, + "505": { + "id": 505, + "name": "item505", + "score": 50500 + }, + "506": { + "id": 506, + "name": "item506", + "score": 50600 + }, + "507": { + "id": 507, + "name": "item507", + "score": 50700 + }, + "508": { + "id": 508, + "name": "item508", + "score": 50800 + }, + "509": { + "id": 509, + "name": "item509", + "score": 50900 + }, + "510": { + "id": 510, + "name": "item510", + "score": 51000 + }, + "511": { + "id": 511, + "name": "item511", + "score": 51100 + }, + "512": { + "id": 512, + "name": "item512", + "score": 51200 + }, + "513": { + "id": 513, + "name": "item513", + "score": 51300 + }, + "514": { + "id": 514, + "name": "item514", + "score": 51400 + }, + "515": { + "id": 515, + "name": "item515", + "score": 51500 + }, + "516": { + "id": 516, + "name": "item516", + "score": 51600 + }, + "517": { + "id": 517, + "name": "item517", + "score": 51700 + }, + "518": { + "id": 518, + "name": "item518", + "score": 51800 + }, + "519": { + "id": 519, + "name": "item519", + "score": 51900 + }, + "520": { + "id": 520, + "name": "item520", + "score": 52000 + }, + "521": { + "id": 521, + "name": "item521", + "score": 52100 + }, + "522": { + "id": 522, + "name": "item522", + "score": 52200 + }, + "523": { + "id": 523, + "name": "item523", + "score": 52300 + }, + "524": { + "id": 524, + "name": "item524", + "score": 52400 + }, + "525": { + "id": 525, + "name": "item525", + "score": 52500 + }, + "526": { + "id": 526, + "name": "item526", + "score": 52600 + }, + "527": { + "id": 527, + "name": "item527", + "score": 52700 + }, + "528": { + "id": 528, + "name": "item528", + "score": 52800 + }, + "529": { + "id": 529, + "name": "item529", + "score": 52900 + }, + "530": { + "id": 530, + "name": "item530", + "score": 53000 + }, + "531": { + "id": 531, + "name": "item531", + "score": 53100 + }, + "532": { + "id": 532, + "name": "item532", + "score": 53200 + }, + "533": { + "id": 533, + "name": "item533", + "score": 53300 + }, + "534": { + "id": 534, + "name": "item534", + "score": 53400 + }, + "535": { + "id": 535, + "name": "item535", + "score": 53500 + }, + "536": { + "id": 536, + "name": "item536", + "score": 53600 + }, + "537": { + "id": 537, + "name": "item537", + "score": 53700 + }, + "538": { + "id": 538, + "name": "item538", + "score": 53800 + }, + "539": { + "id": 539, + "name": "item539", + "score": 53900 + }, + "540": { + "id": 540, + "name": "item540", + "score": 54000 + }, + "541": { + "id": 541, + "name": "item541", + "score": 54100 + }, + "542": { + "id": 542, + "name": "item542", + "score": 54200 + }, + "543": { + "id": 543, + "name": "item543", + "score": 54300 + }, + "544": { + "id": 544, + "name": "item544", + "score": 54400 + }, + "545": { + "id": 545, + "name": "item545", + "score": 54500 + }, + "546": { + "id": 546, + "name": "item546", + "score": 54600 + }, + "547": { + "id": 547, + "name": "item547", + "score": 54700 + }, + "548": { + "id": 548, + "name": "item548", + "score": 54800 + }, + "549": { + "id": 549, + "name": "item549", + "score": 54900 + }, + "550": { + "id": 550, + "name": "item550", + "score": 55000 + }, + "551": { + "id": 551, + "name": "item551", + "score": 55100 + }, + "552": { + "id": 552, + "name": "item552", + "score": 55200 + }, + "553": { + "id": 553, + "name": "item553", + "score": 55300 + }, + "554": { + "id": 554, + "name": "item554", + "score": 55400 + }, + "555": { + "id": 555, + "name": "item555", + "score": 55500 + }, + "556": { + "id": 556, + "name": "item556", + "score": 55600 + }, + "557": { + "id": 557, + "name": "item557", + "score": 55700 + }, + "558": { + "id": 558, + "name": "item558", + "score": 55800 + }, + "559": { + "id": 559, + "name": "item559", + "score": 55900 + }, + "560": { + "id": 560, + "name": "item560", + "score": 56000 + }, + "561": { + "id": 561, + "name": "item561", + "score": 56100 + }, + "562": { + "id": 562, + "name": "item562", + "score": 56200 + }, + "563": { + "id": 563, + "name": "item563", + "score": 56300 + }, + "564": { + "id": 564, + "name": "item564", + "score": 56400 + }, + "565": { + "id": 565, + "name": "item565", + "score": 56500 + }, + "566": { + "id": 566, + "name": "item566", + "score": 56600 + }, + "567": { + "id": 567, + "name": "item567", + "score": 56700 + }, + "568": { + "id": 568, + "name": "item568", + "score": 56800 + }, + "569": { + "id": 569, + "name": "item569", + "score": 56900 + }, + "570": { + "id": 570, + "name": "item570", + "score": 57000 + }, + "571": { + "id": 571, + "name": "item571", + "score": 57100 + }, + "572": { + "id": 572, + "name": "item572", + "score": 57200 + }, + "573": { + "id": 573, + "name": "item573", + "score": 57300 + }, + "574": { + "id": 574, + "name": "item574", + "score": 57400 + }, + "575": { + "id": 575, + "name": "item575", + "score": 57500 + }, + "576": { + "id": 576, + "name": "item576", + "score": 57600 + }, + "577": { + "id": 577, + "name": "item577", + "score": 57700 + }, + "578": { + "id": 578, + "name": "item578", + "score": 57800 + }, + "579": { + "id": 579, + "name": "item579", + "score": 57900 + }, + "580": { + "id": 580, + "name": "item580", + "score": 58000 + }, + "581": { + "id": 581, + "name": "item581", + "score": 58100 + }, + "582": { + "id": 582, + "name": "item582", + "score": 58200 + }, + "583": { + "id": 583, + "name": "item583", + "score": 58300 + }, + "584": { + "id": 584, + "name": "item584", + "score": 58400 + }, + "585": { + "id": 585, + "name": "item585", + "score": 58500 + }, + "586": { + "id": 586, + "name": "item586", + "score": 58600 + }, + "587": { + "id": 587, + "name": "item587", + "score": 58700 + }, + "588": { + "id": 588, + "name": "item588", + "score": 58800 + }, + "589": { + "id": 589, + "name": "item589", + "score": 58900 + }, + "590": { + "id": 590, + "name": "item590", + "score": 59000 + }, + "591": { + "id": 591, + "name": "item591", + "score": 59100 + }, + "592": { + "id": 592, + "name": "item592", + "score": 59200 + }, + "593": { + "id": 593, + "name": "item593", + "score": 59300 + }, + "594": { + "id": 594, + "name": "item594", + "score": 59400 + }, + "595": { + "id": 595, + "name": "item595", + "score": 59500 + }, + "596": { + "id": 596, + "name": "item596", + "score": 59600 + }, + "597": { + "id": 597, + "name": "item597", + "score": 59700 + }, + "598": { + "id": 598, + "name": "item598", + "score": 59800 + }, + "599": { + "id": 599, + "name": "item599", + "score": 59900 + }, + "600": { + "id": 600, + "name": "item600", + "score": 60000 + }, + "601": { + "id": 601, + "name": "item601", + "score": 60100 + }, + "602": { + "id": 602, + "name": "item602", + "score": 60200 + }, + "603": { + "id": 603, + "name": "item603", + "score": 60300 + }, + "604": { + "id": 604, + "name": "item604", + "score": 60400 + }, + "605": { + "id": 605, + "name": "item605", + "score": 60500 + }, + "606": { + "id": 606, + "name": "item606", + "score": 60600 + }, + "607": { + "id": 607, + "name": "item607", + "score": 60700 + }, + "608": { + "id": 608, + "name": "item608", + "score": 60800 + }, + "609": { + "id": 609, + "name": "item609", + "score": 60900 + }, + "610": { + "id": 610, + "name": "item610", + "score": 61000 + }, + "611": { + "id": 611, + "name": "item611", + "score": 61100 + }, + "612": { + "id": 612, + "name": "item612", + "score": 61200 + }, + "613": { + "id": 613, + "name": "item613", + "score": 61300 + }, + "614": { + "id": 614, + "name": "item614", + "score": 61400 + }, + "615": { + "id": 615, + "name": "item615", + "score": 61500 + }, + "616": { + "id": 616, + "name": "item616", + "score": 61600 + }, + "617": { + "id": 617, + "name": "item617", + "score": 61700 + }, + "618": { + "id": 618, + "name": "item618", + "score": 61800 + }, + "619": { + "id": 619, + "name": "item619", + "score": 61900 + }, + "620": { + "id": 620, + "name": "item620", + "score": 62000 + }, + "621": { + "id": 621, + "name": "item621", + "score": 62100 + }, + "622": { + "id": 622, + "name": "item622", + "score": 62200 + }, + "623": { + "id": 623, + "name": "item623", + "score": 62300 + }, + "624": { + "id": 624, + "name": "item624", + "score": 62400 + }, + "625": { + "id": 625, + "name": "item625", + "score": 62500 + }, + "626": { + "id": 626, + "name": "item626", + "score": 62600 + }, + "627": { + "id": 627, + "name": "item627", + "score": 62700 + }, + "628": { + "id": 628, + "name": "item628", + "score": 62800 + }, + "629": { + "id": 629, + "name": "item629", + "score": 62900 + }, + "630": { + "id": 630, + "name": "item630", + "score": 63000 + }, + "631": { + "id": 631, + "name": "item631", + "score": 63100 + }, + "632": { + "id": 632, + "name": "item632", + "score": 63200 + }, + "633": { + "id": 633, + "name": "item633", + "score": 63300 + }, + "634": { + "id": 634, + "name": "item634", + "score": 63400 + }, + "635": { + "id": 635, + "name": "item635", + "score": 63500 + }, + "636": { + "id": 636, + "name": "item636", + "score": 63600 + }, + "637": { + "id": 637, + "name": "item637", + "score": 63700 + }, + "638": { + "id": 638, + "name": "item638", + "score": 63800 + }, + "639": { + "id": 639, + "name": "item639", + "score": 63900 + }, + "640": { + "id": 640, + "name": "item640", + "score": 64000 + }, + "641": { + "id": 641, + "name": "item641", + "score": 64100 + }, + "642": { + "id": 642, + "name": "item642", + "score": 64200 + }, + "643": { + "id": 643, + "name": "item643", + "score": 64300 + }, + "644": { + "id": 644, + "name": "item644", + "score": 64400 + }, + "645": { + "id": 645, + "name": "item645", + "score": 64500 + }, + "646": { + "id": 646, + "name": "item646", + "score": 64600 + }, + "647": { + "id": 647, + "name": "item647", + "score": 64700 + }, + "648": { + "id": 648, + "name": "item648", + "score": 64800 + }, + "649": { + "id": 649, + "name": "item649", + "score": 64900 + }, + "650": { + "id": 650, + "name": "item650", + "score": 65000 + }, + "651": { + "id": 651, + "name": "item651", + "score": 65100 + }, + "652": { + "id": 652, + "name": "item652", + "score": 65200 + }, + "653": { + "id": 653, + "name": "item653", + "score": 65300 + }, + "654": { + "id": 654, + "name": "item654", + "score": 65400 + }, + "655": { + "id": 655, + "name": "item655", + "score": 65500 + }, + "656": { + "id": 656, + "name": "item656", + "score": 65600 + }, + "657": { + "id": 657, + "name": "item657", + "score": 65700 + }, + "658": { + "id": 658, + "name": "item658", + "score": 65800 + }, + "659": { + "id": 659, + "name": "item659", + "score": 65900 + }, + "660": { + "id": 660, + "name": "item660", + "score": 66000 + }, + "661": { + "id": 661, + "name": "item661", + "score": 66100 + }, + "662": { + "id": 662, + "name": "item662", + "score": 66200 + }, + "663": { + "id": 663, + "name": "item663", + "score": 66300 + }, + "664": { + "id": 664, + "name": "item664", + "score": 66400 + }, + "665": { + "id": 665, + "name": "item665", + "score": 66500 + }, + "666": { + "id": 666, + "name": "item666", + "score": 66600 + }, + "667": { + "id": 667, + "name": "item667", + "score": 66700 + }, + "668": { + "id": 668, + "name": "item668", + "score": 66800 + }, + "669": { + "id": 669, + "name": "item669", + "score": 66900 + }, + "670": { + "id": 670, + "name": "item670", + "score": 67000 + }, + "671": { + "id": 671, + "name": "item671", + "score": 67100 + }, + "672": { + "id": 672, + "name": "item672", + "score": 67200 + }, + "673": { + "id": 673, + "name": "item673", + "score": 67300 + }, + "674": { + "id": 674, + "name": "item674", + "score": 67400 + }, + "675": { + "id": 675, + "name": "item675", + "score": 67500 + }, + "676": { + "id": 676, + "name": "item676", + "score": 67600 + }, + "677": { + "id": 677, + "name": "item677", + "score": 67700 + }, + "678": { + "id": 678, + "name": "item678", + "score": 67800 + }, + "679": { + "id": 679, + "name": "item679", + "score": 67900 + }, + "680": { + "id": 680, + "name": "item680", + "score": 68000 + }, + "681": { + "id": 681, + "name": "item681", + "score": 68100 + }, + "682": { + "id": 682, + "name": "item682", + "score": 68200 + }, + "683": { + "id": 683, + "name": "item683", + "score": 68300 + }, + "684": { + "id": 684, + "name": "item684", + "score": 68400 + }, + "685": { + "id": 685, + "name": "item685", + "score": 68500 + }, + "686": { + "id": 686, + "name": "item686", + "score": 68600 + }, + "687": { + "id": 687, + "name": "item687", + "score": 68700 + }, + "688": { + "id": 688, + "name": "item688", + "score": 68800 + }, + "689": { + "id": 689, + "name": "item689", + "score": 68900 + }, + "690": { + "id": 690, + "name": "item690", + "score": 69000 + }, + "691": { + "id": 691, + "name": "item691", + "score": 69100 + }, + "692": { + "id": 692, + "name": "item692", + "score": 69200 + }, + "693": { + "id": 693, + "name": "item693", + "score": 69300 + }, + "694": { + "id": 694, + "name": "item694", + "score": 69400 + }, + "695": { + "id": 695, + "name": "item695", + "score": 69500 + }, + "696": { + "id": 696, + "name": "item696", + "score": 69600 + }, + "697": { + "id": 697, + "name": "item697", + "score": 69700 + }, + "698": { + "id": 698, + "name": "item698", + "score": 69800 + }, + "699": { + "id": 699, + "name": "item699", + "score": 69900 + }, + "700": { + "id": 700, + "name": "item700", + "score": 70000 + }, + "701": { + "id": 701, + "name": "item701", + "score": 70100 + }, + "702": { + "id": 702, + "name": "item702", + "score": 70200 + }, + "703": { + "id": 703, + "name": "item703", + "score": 70300 + }, + "704": { + "id": 704, + "name": "item704", + "score": 70400 + }, + "705": { + "id": 705, + "name": "item705", + "score": 70500 + }, + "706": { + "id": 706, + "name": "item706", + "score": 70600 + }, + "707": { + "id": 707, + "name": "item707", + "score": 70700 + }, + "708": { + "id": 708, + "name": "item708", + "score": 70800 + }, + "709": { + "id": 709, + "name": "item709", + "score": 70900 + }, + "710": { + "id": 710, + "name": "item710", + "score": 71000 + }, + "711": { + "id": 711, + "name": "item711", + "score": 71100 + }, + "712": { + "id": 712, + "name": "item712", + "score": 71200 + }, + "713": { + "id": 713, + "name": "item713", + "score": 71300 + }, + "714": { + "id": 714, + "name": "item714", + "score": 71400 + }, + "715": { + "id": 715, + "name": "item715", + "score": 71500 + }, + "716": { + "id": 716, + "name": "item716", + "score": 71600 + }, + "717": { + "id": 717, + "name": "item717", + "score": 71700 + }, + "718": { + "id": 718, + "name": "item718", + "score": 71800 + }, + "719": { + "id": 719, + "name": "item719", + "score": 71900 + }, + "720": { + "id": 720, + "name": "item720", + "score": 72000 + }, + "721": { + "id": 721, + "name": "item721", + "score": 72100 + }, + "722": { + "id": 722, + "name": "item722", + "score": 72200 + }, + "723": { + "id": 723, + "name": "item723", + "score": 72300 + }, + "724": { + "id": 724, + "name": "item724", + "score": 72400 + }, + "725": { + "id": 725, + "name": "item725", + "score": 72500 + }, + "726": { + "id": 726, + "name": "item726", + "score": 72600 + }, + "727": { + "id": 727, + "name": "item727", + "score": 72700 + }, + "728": { + "id": 728, + "name": "item728", + "score": 72800 + }, + "729": { + "id": 729, + "name": "item729", + "score": 72900 + }, + "730": { + "id": 730, + "name": "item730", + "score": 73000 + }, + "731": { + "id": 731, + "name": "item731", + "score": 73100 + }, + "732": { + "id": 732, + "name": "item732", + "score": 73200 + }, + "733": { + "id": 733, + "name": "item733", + "score": 73300 + }, + "734": { + "id": 734, + "name": "item734", + "score": 73400 + }, + "735": { + "id": 735, + "name": "item735", + "score": 73500 + }, + "736": { + "id": 736, + "name": "item736", + "score": 73600 + }, + "737": { + "id": 737, + "name": "item737", + "score": 73700 + }, + "738": { + "id": 738, + "name": "item738", + "score": 73800 + }, + "739": { + "id": 739, + "name": "item739", + "score": 73900 + }, + "740": { + "id": 740, + "name": "item740", + "score": 74000 + }, + "741": { + "id": 741, + "name": "item741", + "score": 74100 + }, + "742": { + "id": 742, + "name": "item742", + "score": 74200 + }, + "743": { + "id": 743, + "name": "item743", + "score": 74300 + }, + "744": { + "id": 744, + "name": "item744", + "score": 74400 + }, + "745": { + "id": 745, + "name": "item745", + "score": 74500 + }, + "746": { + "id": 746, + "name": "item746", + "score": 74600 + }, + "747": { + "id": 747, + "name": "item747", + "score": 74700 + }, + "748": { + "id": 748, + "name": "item748", + "score": 74800 + }, + "749": { + "id": 749, + "name": "item749", + "score": 74900 + }, + "750": { + "id": 750, + "name": "item750", + "score": 75000 + }, + "751": { + "id": 751, + "name": "item751", + "score": 75100 + }, + "752": { + "id": 752, + "name": "item752", + "score": 75200 + }, + "753": { + "id": 753, + "name": "item753", + "score": 75300 + }, + "754": { + "id": 754, + "name": "item754", + "score": 75400 + }, + "755": { + "id": 755, + "name": "item755", + "score": 75500 + }, + "756": { + "id": 756, + "name": "item756", + "score": 75600 + }, + "757": { + "id": 757, + "name": "item757", + "score": 75700 + }, + "758": { + "id": 758, + "name": "item758", + "score": 75800 + }, + "759": { + "id": 759, + "name": "item759", + "score": 75900 + }, + "760": { + "id": 760, + "name": "item760", + "score": 76000 + }, + "761": { + "id": 761, + "name": "item761", + "score": 76100 + }, + "762": { + "id": 762, + "name": "item762", + "score": 76200 + }, + "763": { + "id": 763, + "name": "item763", + "score": 76300 + }, + "764": { + "id": 764, + "name": "item764", + "score": 76400 + }, + "765": { + "id": 765, + "name": "item765", + "score": 76500 + }, + "766": { + "id": 766, + "name": "item766", + "score": 76600 + }, + "767": { + "id": 767, + "name": "item767", + "score": 76700 + }, + "768": { + "id": 768, + "name": "item768", + "score": 76800 + }, + "769": { + "id": 769, + "name": "item769", + "score": 76900 + }, + "770": { + "id": 770, + "name": "item770", + "score": 77000 + }, + "771": { + "id": 771, + "name": "item771", + "score": 77100 + }, + "772": { + "id": 772, + "name": "item772", + "score": 77200 + }, + "773": { + "id": 773, + "name": "item773", + "score": 77300 + }, + "774": { + "id": 774, + "name": "item774", + "score": 77400 + }, + "775": { + "id": 775, + "name": "item775", + "score": 77500 + }, + "776": { + "id": 776, + "name": "item776", + "score": 77600 + }, + "777": { + "id": 777, + "name": "item777", + "score": 77700 + }, + "778": { + "id": 778, + "name": "item778", + "score": 77800 + }, + "779": { + "id": 779, + "name": "item779", + "score": 77900 + }, + "780": { + "id": 780, + "name": "item780", + "score": 78000 + }, + "781": { + "id": 781, + "name": "item781", + "score": 78100 + }, + "782": { + "id": 782, + "name": "item782", + "score": 78200 + }, + "783": { + "id": 783, + "name": "item783", + "score": 78300 + }, + "784": { + "id": 784, + "name": "item784", + "score": 78400 + }, + "785": { + "id": 785, + "name": "item785", + "score": 78500 + }, + "786": { + "id": 786, + "name": "item786", + "score": 78600 + }, + "787": { + "id": 787, + "name": "item787", + "score": 78700 + }, + "788": { + "id": 788, + "name": "item788", + "score": 78800 + }, + "789": { + "id": 789, + "name": "item789", + "score": 78900 + }, + "790": { + "id": 790, + "name": "item790", + "score": 79000 + }, + "791": { + "id": 791, + "name": "item791", + "score": 79100 + }, + "792": { + "id": 792, + "name": "item792", + "score": 79200 + }, + "793": { + "id": 793, + "name": "item793", + "score": 79300 + }, + "794": { + "id": 794, + "name": "item794", + "score": 79400 + }, + "795": { + "id": 795, + "name": "item795", + "score": 79500 + }, + "796": { + "id": 796, + "name": "item796", + "score": 79600 + }, + "797": { + "id": 797, + "name": "item797", + "score": 79700 + }, + "798": { + "id": 798, + "name": "item798", + "score": 79800 + }, + "799": { + "id": 799, + "name": "item799", + "score": 79900 + }, + "800": { + "id": 800, + "name": "item800", + "score": 80000 + }, + "801": { + "id": 801, + "name": "item801", + "score": 80100 + }, + "802": { + "id": 802, + "name": "item802", + "score": 80200 + }, + "803": { + "id": 803, + "name": "item803", + "score": 80300 + }, + "804": { + "id": 804, + "name": "item804", + "score": 80400 + }, + "805": { + "id": 805, + "name": "item805", + "score": 80500 + }, + "806": { + "id": 806, + "name": "item806", + "score": 80600 + }, + "807": { + "id": 807, + "name": "item807", + "score": 80700 + }, + "808": { + "id": 808, + "name": "item808", + "score": 80800 + }, + "809": { + "id": 809, + "name": "item809", + "score": 80900 + }, + "810": { + "id": 810, + "name": "item810", + "score": 81000 + }, + "811": { + "id": 811, + "name": "item811", + "score": 81100 + }, + "812": { + "id": 812, + "name": "item812", + "score": 81200 + }, + "813": { + "id": 813, + "name": "item813", + "score": 81300 + }, + "814": { + "id": 814, + "name": "item814", + "score": 81400 + }, + "815": { + "id": 815, + "name": "item815", + "score": 81500 + }, + "816": { + "id": 816, + "name": "item816", + "score": 81600 + }, + "817": { + "id": 817, + "name": "item817", + "score": 81700 + }, + "818": { + "id": 818, + "name": "item818", + "score": 81800 + }, + "819": { + "id": 819, + "name": "item819", + "score": 81900 + }, + "820": { + "id": 820, + "name": "item820", + "score": 82000 + }, + "821": { + "id": 821, + "name": "item821", + "score": 82100 + }, + "822": { + "id": 822, + "name": "item822", + "score": 82200 + }, + "823": { + "id": 823, + "name": "item823", + "score": 82300 + }, + "824": { + "id": 824, + "name": "item824", + "score": 82400 + }, + "825": { + "id": 825, + "name": "item825", + "score": 82500 + }, + "826": { + "id": 826, + "name": "item826", + "score": 82600 + }, + "827": { + "id": 827, + "name": "item827", + "score": 82700 + }, + "828": { + "id": 828, + "name": "item828", + "score": 82800 + }, + "829": { + "id": 829, + "name": "item829", + "score": 82900 + }, + "830": { + "id": 830, + "name": "item830", + "score": 83000 + }, + "831": { + "id": 831, + "name": "item831", + "score": 83100 + }, + "832": { + "id": 832, + "name": "item832", + "score": 83200 + }, + "833": { + "id": 833, + "name": "item833", + "score": 83300 + }, + "834": { + "id": 834, + "name": "item834", + "score": 83400 + }, + "835": { + "id": 835, + "name": "item835", + "score": 83500 + }, + "836": { + "id": 836, + "name": "item836", + "score": 83600 + }, + "837": { + "id": 837, + "name": "item837", + "score": 83700 + }, + "838": { + "id": 838, + "name": "item838", + "score": 83800 + }, + "839": { + "id": 839, + "name": "item839", + "score": 83900 + }, + "840": { + "id": 840, + "name": "item840", + "score": 84000 + }, + "841": { + "id": 841, + "name": "item841", + "score": 84100 + }, + "842": { + "id": 842, + "name": "item842", + "score": 84200 + }, + "843": { + "id": 843, + "name": "item843", + "score": 84300 + }, + "844": { + "id": 844, + "name": "item844", + "score": 84400 + }, + "845": { + "id": 845, + "name": "item845", + "score": 84500 + }, + "846": { + "id": 846, + "name": "item846", + "score": 84600 + }, + "847": { + "id": 847, + "name": "item847", + "score": 84700 + }, + "848": { + "id": 848, + "name": "item848", + "score": 84800 + }, + "849": { + "id": 849, + "name": "item849", + "score": 84900 + }, + "850": { + "id": 850, + "name": "item850", + "score": 85000 + }, + "851": { + "id": 851, + "name": "item851", + "score": 85100 + }, + "852": { + "id": 852, + "name": "item852", + "score": 85200 + }, + "853": { + "id": 853, + "name": "item853", + "score": 85300 + }, + "854": { + "id": 854, + "name": "item854", + "score": 85400 + }, + "855": { + "id": 855, + "name": "item855", + "score": 85500 + }, + "856": { + "id": 856, + "name": "item856", + "score": 85600 + }, + "857": { + "id": 857, + "name": "item857", + "score": 85700 + }, + "858": { + "id": 858, + "name": "item858", + "score": 85800 + }, + "859": { + "id": 859, + "name": "item859", + "score": 85900 + }, + "860": { + "id": 860, + "name": "item860", + "score": 86000 + }, + "861": { + "id": 861, + "name": "item861", + "score": 86100 + }, + "862": { + "id": 862, + "name": "item862", + "score": 86200 + }, + "863": { + "id": 863, + "name": "item863", + "score": 86300 + }, + "864": { + "id": 864, + "name": "item864", + "score": 86400 + }, + "865": { + "id": 865, + "name": "item865", + "score": 86500 + }, + "866": { + "id": 866, + "name": "item866", + "score": 86600 + }, + "867": { + "id": 867, + "name": "item867", + "score": 86700 + }, + "868": { + "id": 868, + "name": "item868", + "score": 86800 + }, + "869": { + "id": 869, + "name": "item869", + "score": 86900 + }, + "870": { + "id": 870, + "name": "item870", + "score": 87000 + }, + "871": { + "id": 871, + "name": "item871", + "score": 87100 + }, + "872": { + "id": 872, + "name": "item872", + "score": 87200 + }, + "873": { + "id": 873, + "name": "item873", + "score": 87300 + }, + "874": { + "id": 874, + "name": "item874", + "score": 87400 + }, + "875": { + "id": 875, + "name": "item875", + "score": 87500 + }, + "876": { + "id": 876, + "name": "item876", + "score": 87600 + }, + "877": { + "id": 877, + "name": "item877", + "score": 87700 + }, + "878": { + "id": 878, + "name": "item878", + "score": 87800 + }, + "879": { + "id": 879, + "name": "item879", + "score": 87900 + }, + "880": { + "id": 880, + "name": "item880", + "score": 88000 + }, + "881": { + "id": 881, + "name": "item881", + "score": 88100 + }, + "882": { + "id": 882, + "name": "item882", + "score": 88200 + }, + "883": { + "id": 883, + "name": "item883", + "score": 88300 + }, + "884": { + "id": 884, + "name": "item884", + "score": 88400 + }, + "885": { + "id": 885, + "name": "item885", + "score": 88500 + }, + "886": { + "id": 886, + "name": "item886", + "score": 88600 + }, + "887": { + "id": 887, + "name": "item887", + "score": 88700 + }, + "888": { + "id": 888, + "name": "item888", + "score": 88800 + }, + "889": { + "id": 889, + "name": "item889", + "score": 88900 + }, + "890": { + "id": 890, + "name": "item890", + "score": 89000 + }, + "891": { + "id": 891, + "name": "item891", + "score": 89100 + }, + "892": { + "id": 892, + "name": "item892", + "score": 89200 + }, + "893": { + "id": 893, + "name": "item893", + "score": 89300 + }, + "894": { + "id": 894, + "name": "item894", + "score": 89400 + }, + "895": { + "id": 895, + "name": "item895", + "score": 89500 + }, + "896": { + "id": 896, + "name": "item896", + "score": 89600 + }, + "897": { + "id": 897, + "name": "item897", + "score": 89700 + }, + "898": { + "id": 898, + "name": "item898", + "score": 89800 + }, + "899": { + "id": 899, + "name": "item899", + "score": 89900 + }, + "900": { + "id": 900, + "name": "item900", + "score": 90000 + }, + "901": { + "id": 901, + "name": "item901", + "score": 90100 + }, + "902": { + "id": 902, + "name": "item902", + "score": 90200 + }, + "903": { + "id": 903, + "name": "item903", + "score": 90300 + }, + "904": { + "id": 904, + "name": "item904", + "score": 90400 + }, + "905": { + "id": 905, + "name": "item905", + "score": 90500 + }, + "906": { + "id": 906, + "name": "item906", + "score": 90600 + }, + "907": { + "id": 907, + "name": "item907", + "score": 90700 + }, + "908": { + "id": 908, + "name": "item908", + "score": 90800 + }, + "909": { + "id": 909, + "name": "item909", + "score": 90900 + }, + "910": { + "id": 910, + "name": "item910", + "score": 91000 + }, + "911": { + "id": 911, + "name": "item911", + "score": 91100 + }, + "912": { + "id": 912, + "name": "item912", + "score": 91200 + }, + "913": { + "id": 913, + "name": "item913", + "score": 91300 + }, + "914": { + "id": 914, + "name": "item914", + "score": 91400 + }, + "915": { + "id": 915, + "name": "item915", + "score": 91500 + }, + "916": { + "id": 916, + "name": "item916", + "score": 91600 + }, + "917": { + "id": 917, + "name": "item917", + "score": 91700 + }, + "918": { + "id": 918, + "name": "item918", + "score": 91800 + }, + "919": { + "id": 919, + "name": "item919", + "score": 91900 + }, + "920": { + "id": 920, + "name": "item920", + "score": 92000 + }, + "921": { + "id": 921, + "name": "item921", + "score": 92100 + }, + "922": { + "id": 922, + "name": "item922", + "score": 92200 + }, + "923": { + "id": 923, + "name": "item923", + "score": 92300 + }, + "924": { + "id": 924, + "name": "item924", + "score": 92400 + }, + "925": { + "id": 925, + "name": "item925", + "score": 92500 + }, + "926": { + "id": 926, + "name": "item926", + "score": 92600 + }, + "927": { + "id": 927, + "name": "item927", + "score": 92700 + }, + "928": { + "id": 928, + "name": "item928", + "score": 92800 + }, + "929": { + "id": 929, + "name": "item929", + "score": 92900 + }, + "930": { + "id": 930, + "name": "item930", + "score": 93000 + }, + "931": { + "id": 931, + "name": "item931", + "score": 93100 + }, + "932": { + "id": 932, + "name": "item932", + "score": 93200 + }, + "933": { + "id": 933, + "name": "item933", + "score": 93300 + }, + "934": { + "id": 934, + "name": "item934", + "score": 93400 + }, + "935": { + "id": 935, + "name": "item935", + "score": 93500 + }, + "936": { + "id": 936, + "name": "item936", + "score": 93600 + }, + "937": { + "id": 937, + "name": "item937", + "score": 93700 + }, + "938": { + "id": 938, + "name": "item938", + "score": 93800 + }, + "939": { + "id": 939, + "name": "item939", + "score": 93900 + }, + "940": { + "id": 940, + "name": "item940", + "score": 94000 + }, + "941": { + "id": 941, + "name": "item941", + "score": 94100 + }, + "942": { + "id": 942, + "name": "item942", + "score": 94200 + }, + "943": { + "id": 943, + "name": "item943", + "score": 94300 + }, + "944": { + "id": 944, + "name": "item944", + "score": 94400 + }, + "945": { + "id": 945, + "name": "item945", + "score": 94500 + }, + "946": { + "id": 946, + "name": "item946", + "score": 94600 + }, + "947": { + "id": 947, + "name": "item947", + "score": 94700 + }, + "948": { + "id": 948, + "name": "item948", + "score": 94800 + }, + "949": { + "id": 949, + "name": "item949", + "score": 94900 + }, + "950": { + "id": 950, + "name": "item950", + "score": 95000 + }, + "951": { + "id": 951, + "name": "item951", + "score": 95100 + }, + "952": { + "id": 952, + "name": "item952", + "score": 95200 + }, + "953": { + "id": 953, + "name": "item953", + "score": 95300 + }, + "954": { + "id": 954, + "name": "item954", + "score": 95400 + }, + "955": { + "id": 955, + "name": "item955", + "score": 95500 + }, + "956": { + "id": 956, + "name": "item956", + "score": 95600 + }, + "957": { + "id": 957, + "name": "item957", + "score": 95700 + }, + "958": { + "id": 958, + "name": "item958", + "score": 95800 + }, + "959": { + "id": 959, + "name": "item959", + "score": 95900 + }, + "960": { + "id": 960, + "name": "item960", + "score": 96000 + }, + "961": { + "id": 961, + "name": "item961", + "score": 96100 + }, + "962": { + "id": 962, + "name": "item962", + "score": 96200 + }, + "963": { + "id": 963, + "name": "item963", + "score": 96300 + }, + "964": { + "id": 964, + "name": "item964", + "score": 96400 + }, + "965": { + "id": 965, + "name": "item965", + "score": 96500 + }, + "966": { + "id": 966, + "name": "item966", + "score": 96600 + }, + "967": { + "id": 967, + "name": "item967", + "score": 96700 + }, + "968": { + "id": 968, + "name": "item968", + "score": 96800 + }, + "969": { + "id": 969, + "name": "item969", + "score": 96900 + }, + "970": { + "id": 970, + "name": "item970", + "score": 97000 + }, + "971": { + "id": 971, + "name": "item971", + "score": 97100 + }, + "972": { + "id": 972, + "name": "item972", + "score": 97200 + }, + "973": { + "id": 973, + "name": "item973", + "score": 97300 + }, + "974": { + "id": 974, + "name": "item974", + "score": 97400 + }, + "975": { + "id": 975, + "name": "item975", + "score": 97500 + }, + "976": { + "id": 976, + "name": "item976", + "score": 97600 + }, + "977": { + "id": 977, + "name": "item977", + "score": 97700 + }, + "978": { + "id": 978, + "name": "item978", + "score": 97800 + }, + "979": { + "id": 979, + "name": "item979", + "score": 97900 + }, + "980": { + "id": 980, + "name": "item980", + "score": 98000 + }, + "981": { + "id": 981, + "name": "item981", + "score": 98100 + }, + "982": { + "id": 982, + "name": "item982", + "score": 98200 + }, + "983": { + "id": 983, + "name": "item983", + "score": 98300 + }, + "984": { + "id": 984, + "name": "item984", + "score": 98400 + }, + "985": { + "id": 985, + "name": "item985", + "score": 98500 + }, + "986": { + "id": 986, + "name": "item986", + "score": 98600 + }, + "987": { + "id": 987, + "name": "item987", + "score": 98700 + }, + "988": { + "id": 988, + "name": "item988", + "score": 98800 + }, + "989": { + "id": 989, + "name": "item989", + "score": 98900 + }, + "990": { + "id": 990, + "name": "item990", + "score": 99000 + }, + "991": { + "id": 991, + "name": "item991", + "score": 99100 + }, + "992": { + "id": 992, + "name": "item992", + "score": 99200 + }, + "993": { + "id": 993, + "name": "item993", + "score": 99300 + }, + "994": { + "id": 994, + "name": "item994", + "score": 99400 + }, + "995": { + "id": 995, + "name": "item995", + "score": 99500 + }, + "996": { + "id": 996, + "name": "item996", + "score": 99600 + }, + "997": { + "id": 997, + "name": "item997", + "score": 99700 + }, + "998": { + "id": 998, + "name": "item998", + "score": 99800 + }, + "999": { + "id": 999, + "name": "item999", + "score": 99900 + }, + "1000": { + "id": 1000, + "name": "item1000", + "score": 100000 + }, + "1001": { + "id": 1001, + "name": "item1001", + "score": 100100 + }, + "1002": { + "id": 1002, + "name": "item1002", + "score": 100200 + }, + "1003": { + "id": 1003, + "name": "item1003", + "score": 100300 + }, + "1004": { + "id": 1004, + "name": "item1004", + "score": 100400 + }, + "1005": { + "id": 1005, + "name": "item1005", + "score": 100500 + }, + "1006": { + "id": 1006, + "name": "item1006", + "score": 100600 + }, + "1007": { + "id": 1007, + "name": "item1007", + "score": 100700 + }, + "1008": { + "id": 1008, + "name": "item1008", + "score": 100800 + }, + "1009": { + "id": 1009, + "name": "item1009", + "score": 100900 + }, + "1010": { + "id": 1010, + "name": "item1010", + "score": 101000 + }, + "1011": { + "id": 1011, + "name": "item1011", + "score": 101100 + }, + "1012": { + "id": 1012, + "name": "item1012", + "score": 101200 + }, + "1013": { + "id": 1013, + "name": "item1013", + "score": 101300 + }, + "1014": { + "id": 1014, + "name": "item1014", + "score": 101400 + }, + "1015": { + "id": 1015, + "name": "item1015", + "score": 101500 + }, + "1016": { + "id": 1016, + "name": "item1016", + "score": 101600 + }, + "1017": { + "id": 1017, + "name": "item1017", + "score": 101700 + }, + "1018": { + "id": 1018, + "name": "item1018", + "score": 101800 + }, + "1019": { + "id": 1019, + "name": "item1019", + "score": 101900 + }, + "1020": { + "id": 1020, + "name": "item1020", + "score": 102000 + }, + "1021": { + "id": 1021, + "name": "item1021", + "score": 102100 + }, + "1022": { + "id": 1022, + "name": "item1022", + "score": 102200 + }, + "1023": { + "id": 1023, + "name": "item1023", + "score": 102300 + }, + "1024": { + "id": 1024, + "name": "item1024", + "score": 102400 + } + } +} \ No newline at end of file diff --git a/test/functest/proto/default/excel__parallel__parallel.proto b/test/functest/proto/default/excel__parallel__parallel.proto new file mode 100644 index 00000000..a549ad5a --- /dev/null +++ b/test/functest/proto/default/excel__parallel__parallel.proto @@ -0,0 +1,22 @@ +// Code generated by tableau (protogen v0.9.0). DO NOT EDIT. +// clang-format off + +syntax = "proto3"; + +package protoconf; + +import "tableau/protobuf/tableau.proto"; + +option go_package = "github.com/tableauio/tableau/test/functest/protoconf"; +option (tableau.workbook) = {name:"excel/parallel/Parallel#*.csv" namerow:1 typerow:2 noterow:3 datarow:4 sep:"," subsep:":"}; + +message ParallelConf { + option (tableau.worksheet) = {name:"ParallelConf"}; + + map item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Item { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; // Item ID + string name = 2 [(tableau.field) = {name:"Name"}]; // Item name + int32 score = 3 [(tableau.field) = {name:"Score"}]; // Item score + } +} diff --git a/test/functest/testdata/default/excel/parallel/Parallel#@TABLEAU.csv b/test/functest/testdata/default/excel/parallel/Parallel#@TABLEAU.csv new file mode 100644 index 00000000..e69de29b diff --git a/test/functest/testdata/default/excel/parallel/Parallel#ParallelConf.csv b/test/functest/testdata/default/excel/parallel/Parallel#ParallelConf.csv new file mode 100644 index 00000000..36911c62 --- /dev/null +++ b/test/functest/testdata/default/excel/parallel/Parallel#ParallelConf.csv @@ -0,0 +1,1027 @@ +ID,Name,Score +"map",string,int32 +Item ID,Item name,Item score +1,item1,100 +2,item2,200 +3,item3,300 +4,item4,400 +5,item5,500 +6,item6,600 +7,item7,700 +8,item8,800 +9,item9,900 +10,item10,1000 +11,item11,1100 +12,item12,1200 +13,item13,1300 +14,item14,1400 +15,item15,1500 +16,item16,1600 +17,item17,1700 +18,item18,1800 +19,item19,1900 +20,item20,2000 +21,item21,2100 +22,item22,2200 +23,item23,2300 +24,item24,2400 +25,item25,2500 +26,item26,2600 +27,item27,2700 +28,item28,2800 +29,item29,2900 +30,item30,3000 +31,item31,3100 +32,item32,3200 +33,item33,3300 +34,item34,3400 +35,item35,3500 +36,item36,3600 +37,item37,3700 +38,item38,3800 +39,item39,3900 +40,item40,4000 +41,item41,4100 +42,item42,4200 +43,item43,4300 +44,item44,4400 +45,item45,4500 +46,item46,4600 +47,item47,4700 +48,item48,4800 +49,item49,4900 +50,item50,5000 +51,item51,5100 +52,item52,5200 +53,item53,5300 +54,item54,5400 +55,item55,5500 +56,item56,5600 +57,item57,5700 +58,item58,5800 +59,item59,5900 +60,item60,6000 +61,item61,6100 +62,item62,6200 +63,item63,6300 +64,item64,6400 +65,item65,6500 +66,item66,6600 +67,item67,6700 +68,item68,6800 +69,item69,6900 +70,item70,7000 +71,item71,7100 +72,item72,7200 +73,item73,7300 +74,item74,7400 +75,item75,7500 +76,item76,7600 +77,item77,7700 +78,item78,7800 +79,item79,7900 +80,item80,8000 +81,item81,8100 +82,item82,8200 +83,item83,8300 +84,item84,8400 +85,item85,8500 +86,item86,8600 +87,item87,8700 +88,item88,8800 +89,item89,8900 +90,item90,9000 +91,item91,9100 +92,item92,9200 +93,item93,9300 +94,item94,9400 +95,item95,9500 +96,item96,9600 +97,item97,9700 +98,item98,9800 +99,item99,9900 +100,item100,10000 +101,item101,10100 +102,item102,10200 +103,item103,10300 +104,item104,10400 +105,item105,10500 +106,item106,10600 +107,item107,10700 +108,item108,10800 +109,item109,10900 +110,item110,11000 +111,item111,11100 +112,item112,11200 +113,item113,11300 +114,item114,11400 +115,item115,11500 +116,item116,11600 +117,item117,11700 +118,item118,11800 +119,item119,11900 +120,item120,12000 +121,item121,12100 +122,item122,12200 +123,item123,12300 +124,item124,12400 +125,item125,12500 +126,item126,12600 +127,item127,12700 +128,item128,12800 +129,item129,12900 +130,item130,13000 +131,item131,13100 +132,item132,13200 +133,item133,13300 +134,item134,13400 +135,item135,13500 +136,item136,13600 +137,item137,13700 +138,item138,13800 +139,item139,13900 +140,item140,14000 +141,item141,14100 +142,item142,14200 +143,item143,14300 +144,item144,14400 +145,item145,14500 +146,item146,14600 +147,item147,14700 +148,item148,14800 +149,item149,14900 +150,item150,15000 +151,item151,15100 +152,item152,15200 +153,item153,15300 +154,item154,15400 +155,item155,15500 +156,item156,15600 +157,item157,15700 +158,item158,15800 +159,item159,15900 +160,item160,16000 +161,item161,16100 +162,item162,16200 +163,item163,16300 +164,item164,16400 +165,item165,16500 +166,item166,16600 +167,item167,16700 +168,item168,16800 +169,item169,16900 +170,item170,17000 +171,item171,17100 +172,item172,17200 +173,item173,17300 +174,item174,17400 +175,item175,17500 +176,item176,17600 +177,item177,17700 +178,item178,17800 +179,item179,17900 +180,item180,18000 +181,item181,18100 +182,item182,18200 +183,item183,18300 +184,item184,18400 +185,item185,18500 +186,item186,18600 +187,item187,18700 +188,item188,18800 +189,item189,18900 +190,item190,19000 +191,item191,19100 +192,item192,19200 +193,item193,19300 +194,item194,19400 +195,item195,19500 +196,item196,19600 +197,item197,19700 +198,item198,19800 +199,item199,19900 +200,item200,20000 +201,item201,20100 +202,item202,20200 +203,item203,20300 +204,item204,20400 +205,item205,20500 +206,item206,20600 +207,item207,20700 +208,item208,20800 +209,item209,20900 +210,item210,21000 +211,item211,21100 +212,item212,21200 +213,item213,21300 +214,item214,21400 +215,item215,21500 +216,item216,21600 +217,item217,21700 +218,item218,21800 +219,item219,21900 +220,item220,22000 +221,item221,22100 +222,item222,22200 +223,item223,22300 +224,item224,22400 +225,item225,22500 +226,item226,22600 +227,item227,22700 +228,item228,22800 +229,item229,22900 +230,item230,23000 +231,item231,23100 +232,item232,23200 +233,item233,23300 +234,item234,23400 +235,item235,23500 +236,item236,23600 +237,item237,23700 +238,item238,23800 +239,item239,23900 +240,item240,24000 +241,item241,24100 +242,item242,24200 +243,item243,24300 +244,item244,24400 +245,item245,24500 +246,item246,24600 +247,item247,24700 +248,item248,24800 +249,item249,24900 +250,item250,25000 +251,item251,25100 +252,item252,25200 +253,item253,25300 +254,item254,25400 +255,item255,25500 +256,item256,25600 +257,item257,25700 +258,item258,25800 +259,item259,25900 +260,item260,26000 +261,item261,26100 +262,item262,26200 +263,item263,26300 +264,item264,26400 +265,item265,26500 +266,item266,26600 +267,item267,26700 +268,item268,26800 +269,item269,26900 +270,item270,27000 +271,item271,27100 +272,item272,27200 +273,item273,27300 +274,item274,27400 +275,item275,27500 +276,item276,27600 +277,item277,27700 +278,item278,27800 +279,item279,27900 +280,item280,28000 +281,item281,28100 +282,item282,28200 +283,item283,28300 +284,item284,28400 +285,item285,28500 +286,item286,28600 +287,item287,28700 +288,item288,28800 +289,item289,28900 +290,item290,29000 +291,item291,29100 +292,item292,29200 +293,item293,29300 +294,item294,29400 +295,item295,29500 +296,item296,29600 +297,item297,29700 +298,item298,29800 +299,item299,29900 +300,item300,30000 +301,item301,30100 +302,item302,30200 +303,item303,30300 +304,item304,30400 +305,item305,30500 +306,item306,30600 +307,item307,30700 +308,item308,30800 +309,item309,30900 +310,item310,31000 +311,item311,31100 +312,item312,31200 +313,item313,31300 +314,item314,31400 +315,item315,31500 +316,item316,31600 +317,item317,31700 +318,item318,31800 +319,item319,31900 +320,item320,32000 +321,item321,32100 +322,item322,32200 +323,item323,32300 +324,item324,32400 +325,item325,32500 +326,item326,32600 +327,item327,32700 +328,item328,32800 +329,item329,32900 +330,item330,33000 +331,item331,33100 +332,item332,33200 +333,item333,33300 +334,item334,33400 +335,item335,33500 +336,item336,33600 +337,item337,33700 +338,item338,33800 +339,item339,33900 +340,item340,34000 +341,item341,34100 +342,item342,34200 +343,item343,34300 +344,item344,34400 +345,item345,34500 +346,item346,34600 +347,item347,34700 +348,item348,34800 +349,item349,34900 +350,item350,35000 +351,item351,35100 +352,item352,35200 +353,item353,35300 +354,item354,35400 +355,item355,35500 +356,item356,35600 +357,item357,35700 +358,item358,35800 +359,item359,35900 +360,item360,36000 +361,item361,36100 +362,item362,36200 +363,item363,36300 +364,item364,36400 +365,item365,36500 +366,item366,36600 +367,item367,36700 +368,item368,36800 +369,item369,36900 +370,item370,37000 +371,item371,37100 +372,item372,37200 +373,item373,37300 +374,item374,37400 +375,item375,37500 +376,item376,37600 +377,item377,37700 +378,item378,37800 +379,item379,37900 +380,item380,38000 +381,item381,38100 +382,item382,38200 +383,item383,38300 +384,item384,38400 +385,item385,38500 +386,item386,38600 +387,item387,38700 +388,item388,38800 +389,item389,38900 +390,item390,39000 +391,item391,39100 +392,item392,39200 +393,item393,39300 +394,item394,39400 +395,item395,39500 +396,item396,39600 +397,item397,39700 +398,item398,39800 +399,item399,39900 +400,item400,40000 +401,item401,40100 +402,item402,40200 +403,item403,40300 +404,item404,40400 +405,item405,40500 +406,item406,40600 +407,item407,40700 +408,item408,40800 +409,item409,40900 +410,item410,41000 +411,item411,41100 +412,item412,41200 +413,item413,41300 +414,item414,41400 +415,item415,41500 +416,item416,41600 +417,item417,41700 +418,item418,41800 +419,item419,41900 +420,item420,42000 +421,item421,42100 +422,item422,42200 +423,item423,42300 +424,item424,42400 +425,item425,42500 +426,item426,42600 +427,item427,42700 +428,item428,42800 +429,item429,42900 +430,item430,43000 +431,item431,43100 +432,item432,43200 +433,item433,43300 +434,item434,43400 +435,item435,43500 +436,item436,43600 +437,item437,43700 +438,item438,43800 +439,item439,43900 +440,item440,44000 +441,item441,44100 +442,item442,44200 +443,item443,44300 +444,item444,44400 +445,item445,44500 +446,item446,44600 +447,item447,44700 +448,item448,44800 +449,item449,44900 +450,item450,45000 +451,item451,45100 +452,item452,45200 +453,item453,45300 +454,item454,45400 +455,item455,45500 +456,item456,45600 +457,item457,45700 +458,item458,45800 +459,item459,45900 +460,item460,46000 +461,item461,46100 +462,item462,46200 +463,item463,46300 +464,item464,46400 +465,item465,46500 +466,item466,46600 +467,item467,46700 +468,item468,46800 +469,item469,46900 +470,item470,47000 +471,item471,47100 +472,item472,47200 +473,item473,47300 +474,item474,47400 +475,item475,47500 +476,item476,47600 +477,item477,47700 +478,item478,47800 +479,item479,47900 +480,item480,48000 +481,item481,48100 +482,item482,48200 +483,item483,48300 +484,item484,48400 +485,item485,48500 +486,item486,48600 +487,item487,48700 +488,item488,48800 +489,item489,48900 +490,item490,49000 +491,item491,49100 +492,item492,49200 +493,item493,49300 +494,item494,49400 +495,item495,49500 +496,item496,49600 +497,item497,49700 +498,item498,49800 +499,item499,49900 +500,item500,50000 +501,item501,50100 +502,item502,50200 +503,item503,50300 +504,item504,50400 +505,item505,50500 +506,item506,50600 +507,item507,50700 +508,item508,50800 +509,item509,50900 +510,item510,51000 +511,item511,51100 +512,item512,51200 +513,item513,51300 +514,item514,51400 +515,item515,51500 +516,item516,51600 +517,item517,51700 +518,item518,51800 +519,item519,51900 +520,item520,52000 +521,item521,52100 +522,item522,52200 +523,item523,52300 +524,item524,52400 +525,item525,52500 +526,item526,52600 +527,item527,52700 +528,item528,52800 +529,item529,52900 +530,item530,53000 +531,item531,53100 +532,item532,53200 +533,item533,53300 +534,item534,53400 +535,item535,53500 +536,item536,53600 +537,item537,53700 +538,item538,53800 +539,item539,53900 +540,item540,54000 +541,item541,54100 +542,item542,54200 +543,item543,54300 +544,item544,54400 +545,item545,54500 +546,item546,54600 +547,item547,54700 +548,item548,54800 +549,item549,54900 +550,item550,55000 +551,item551,55100 +552,item552,55200 +553,item553,55300 +554,item554,55400 +555,item555,55500 +556,item556,55600 +557,item557,55700 +558,item558,55800 +559,item559,55900 +560,item560,56000 +561,item561,56100 +562,item562,56200 +563,item563,56300 +564,item564,56400 +565,item565,56500 +566,item566,56600 +567,item567,56700 +568,item568,56800 +569,item569,56900 +570,item570,57000 +571,item571,57100 +572,item572,57200 +573,item573,57300 +574,item574,57400 +575,item575,57500 +576,item576,57600 +577,item577,57700 +578,item578,57800 +579,item579,57900 +580,item580,58000 +581,item581,58100 +582,item582,58200 +583,item583,58300 +584,item584,58400 +585,item585,58500 +586,item586,58600 +587,item587,58700 +588,item588,58800 +589,item589,58900 +590,item590,59000 +591,item591,59100 +592,item592,59200 +593,item593,59300 +594,item594,59400 +595,item595,59500 +596,item596,59600 +597,item597,59700 +598,item598,59800 +599,item599,59900 +600,item600,60000 +601,item601,60100 +602,item602,60200 +603,item603,60300 +604,item604,60400 +605,item605,60500 +606,item606,60600 +607,item607,60700 +608,item608,60800 +609,item609,60900 +610,item610,61000 +611,item611,61100 +612,item612,61200 +613,item613,61300 +614,item614,61400 +615,item615,61500 +616,item616,61600 +617,item617,61700 +618,item618,61800 +619,item619,61900 +620,item620,62000 +621,item621,62100 +622,item622,62200 +623,item623,62300 +624,item624,62400 +625,item625,62500 +626,item626,62600 +627,item627,62700 +628,item628,62800 +629,item629,62900 +630,item630,63000 +631,item631,63100 +632,item632,63200 +633,item633,63300 +634,item634,63400 +635,item635,63500 +636,item636,63600 +637,item637,63700 +638,item638,63800 +639,item639,63900 +640,item640,64000 +641,item641,64100 +642,item642,64200 +643,item643,64300 +644,item644,64400 +645,item645,64500 +646,item646,64600 +647,item647,64700 +648,item648,64800 +649,item649,64900 +650,item650,65000 +651,item651,65100 +652,item652,65200 +653,item653,65300 +654,item654,65400 +655,item655,65500 +656,item656,65600 +657,item657,65700 +658,item658,65800 +659,item659,65900 +660,item660,66000 +661,item661,66100 +662,item662,66200 +663,item663,66300 +664,item664,66400 +665,item665,66500 +666,item666,66600 +667,item667,66700 +668,item668,66800 +669,item669,66900 +670,item670,67000 +671,item671,67100 +672,item672,67200 +673,item673,67300 +674,item674,67400 +675,item675,67500 +676,item676,67600 +677,item677,67700 +678,item678,67800 +679,item679,67900 +680,item680,68000 +681,item681,68100 +682,item682,68200 +683,item683,68300 +684,item684,68400 +685,item685,68500 +686,item686,68600 +687,item687,68700 +688,item688,68800 +689,item689,68900 +690,item690,69000 +691,item691,69100 +692,item692,69200 +693,item693,69300 +694,item694,69400 +695,item695,69500 +696,item696,69600 +697,item697,69700 +698,item698,69800 +699,item699,69900 +700,item700,70000 +701,item701,70100 +702,item702,70200 +703,item703,70300 +704,item704,70400 +705,item705,70500 +706,item706,70600 +707,item707,70700 +708,item708,70800 +709,item709,70900 +710,item710,71000 +711,item711,71100 +712,item712,71200 +713,item713,71300 +714,item714,71400 +715,item715,71500 +716,item716,71600 +717,item717,71700 +718,item718,71800 +719,item719,71900 +720,item720,72000 +721,item721,72100 +722,item722,72200 +723,item723,72300 +724,item724,72400 +725,item725,72500 +726,item726,72600 +727,item727,72700 +728,item728,72800 +729,item729,72900 +730,item730,73000 +731,item731,73100 +732,item732,73200 +733,item733,73300 +734,item734,73400 +735,item735,73500 +736,item736,73600 +737,item737,73700 +738,item738,73800 +739,item739,73900 +740,item740,74000 +741,item741,74100 +742,item742,74200 +743,item743,74300 +744,item744,74400 +745,item745,74500 +746,item746,74600 +747,item747,74700 +748,item748,74800 +749,item749,74900 +750,item750,75000 +751,item751,75100 +752,item752,75200 +753,item753,75300 +754,item754,75400 +755,item755,75500 +756,item756,75600 +757,item757,75700 +758,item758,75800 +759,item759,75900 +760,item760,76000 +761,item761,76100 +762,item762,76200 +763,item763,76300 +764,item764,76400 +765,item765,76500 +766,item766,76600 +767,item767,76700 +768,item768,76800 +769,item769,76900 +770,item770,77000 +771,item771,77100 +772,item772,77200 +773,item773,77300 +774,item774,77400 +775,item775,77500 +776,item776,77600 +777,item777,77700 +778,item778,77800 +779,item779,77900 +780,item780,78000 +781,item781,78100 +782,item782,78200 +783,item783,78300 +784,item784,78400 +785,item785,78500 +786,item786,78600 +787,item787,78700 +788,item788,78800 +789,item789,78900 +790,item790,79000 +791,item791,79100 +792,item792,79200 +793,item793,79300 +794,item794,79400 +795,item795,79500 +796,item796,79600 +797,item797,79700 +798,item798,79800 +799,item799,79900 +800,item800,80000 +801,item801,80100 +802,item802,80200 +803,item803,80300 +804,item804,80400 +805,item805,80500 +806,item806,80600 +807,item807,80700 +808,item808,80800 +809,item809,80900 +810,item810,81000 +811,item811,81100 +812,item812,81200 +813,item813,81300 +814,item814,81400 +815,item815,81500 +816,item816,81600 +817,item817,81700 +818,item818,81800 +819,item819,81900 +820,item820,82000 +821,item821,82100 +822,item822,82200 +823,item823,82300 +824,item824,82400 +825,item825,82500 +826,item826,82600 +827,item827,82700 +828,item828,82800 +829,item829,82900 +830,item830,83000 +831,item831,83100 +832,item832,83200 +833,item833,83300 +834,item834,83400 +835,item835,83500 +836,item836,83600 +837,item837,83700 +838,item838,83800 +839,item839,83900 +840,item840,84000 +841,item841,84100 +842,item842,84200 +843,item843,84300 +844,item844,84400 +845,item845,84500 +846,item846,84600 +847,item847,84700 +848,item848,84800 +849,item849,84900 +850,item850,85000 +851,item851,85100 +852,item852,85200 +853,item853,85300 +854,item854,85400 +855,item855,85500 +856,item856,85600 +857,item857,85700 +858,item858,85800 +859,item859,85900 +860,item860,86000 +861,item861,86100 +862,item862,86200 +863,item863,86300 +864,item864,86400 +865,item865,86500 +866,item866,86600 +867,item867,86700 +868,item868,86800 +869,item869,86900 +870,item870,87000 +871,item871,87100 +872,item872,87200 +873,item873,87300 +874,item874,87400 +875,item875,87500 +876,item876,87600 +877,item877,87700 +878,item878,87800 +879,item879,87900 +880,item880,88000 +881,item881,88100 +882,item882,88200 +883,item883,88300 +884,item884,88400 +885,item885,88500 +886,item886,88600 +887,item887,88700 +888,item888,88800 +889,item889,88900 +890,item890,89000 +891,item891,89100 +892,item892,89200 +893,item893,89300 +894,item894,89400 +895,item895,89500 +896,item896,89600 +897,item897,89700 +898,item898,89800 +899,item899,89900 +900,item900,90000 +901,item901,90100 +902,item902,90200 +903,item903,90300 +904,item904,90400 +905,item905,90500 +906,item906,90600 +907,item907,90700 +908,item908,90800 +909,item909,90900 +910,item910,91000 +911,item911,91100 +912,item912,91200 +913,item913,91300 +914,item914,91400 +915,item915,91500 +916,item916,91600 +917,item917,91700 +918,item918,91800 +919,item919,91900 +920,item920,92000 +921,item921,92100 +922,item922,92200 +923,item923,92300 +924,item924,92400 +925,item925,92500 +926,item926,92600 +927,item927,92700 +928,item928,92800 +929,item929,92900 +930,item930,93000 +931,item931,93100 +932,item932,93200 +933,item933,93300 +934,item934,93400 +935,item935,93500 +936,item936,93600 +937,item937,93700 +938,item938,93800 +939,item939,93900 +940,item940,94000 +941,item941,94100 +942,item942,94200 +943,item943,94300 +944,item944,94400 +945,item945,94500 +946,item946,94600 +947,item947,94700 +948,item948,94800 +949,item949,94900 +950,item950,95000 +951,item951,95100 +952,item952,95200 +953,item953,95300 +954,item954,95400 +955,item955,95500 +956,item956,95600 +957,item957,95700 +958,item958,95800 +959,item959,95900 +960,item960,96000 +961,item961,96100 +962,item962,96200 +963,item963,96300 +964,item964,96400 +965,item965,96500 +966,item966,96600 +967,item967,96700 +968,item968,96800 +969,item969,96900 +970,item970,97000 +971,item971,97100 +972,item972,97200 +973,item973,97300 +974,item974,97400 +975,item975,97500 +976,item976,97600 +977,item977,97700 +978,item978,97800 +979,item979,97900 +980,item980,98000 +981,item981,98100 +982,item982,98200 +983,item983,98300 +984,item984,98400 +985,item985,98500 +986,item986,98600 +987,item987,98700 +988,item988,98800 +989,item989,98900 +990,item990,99000 +991,item991,99100 +992,item992,99200 +993,item993,99300 +994,item994,99400 +995,item995,99500 +996,item996,99600 +997,item997,99700 +998,item998,99800 +999,item999,99900 +1000,item1000,100000 +1001,item1001,100100 +1002,item1002,100200 +1003,item1003,100300 +1004,item1004,100400 +1005,item1005,100500 +1006,item1006,100600 +1007,item1007,100700 +1008,item1008,100800 +1009,item1009,100900 +1010,item1010,101000 +1011,item1011,101100 +1012,item1012,101200 +1013,item1013,101300 +1014,item1014,101400 +1015,item1015,101500 +1016,item1016,101600 +1017,item1017,101700 +1018,item1018,101800 +1019,item1019,101900 +1020,item1020,102000 +1021,item1021,102100 +1022,item1022,102200 +1023,item1023,102300 +1024,item1024,102400