Commit 27e751c
authored
Optimize gRPC Response Parsing Performance (#553)
## Problem
The current implementation uses `json_format.MessageToDict` to convert
entire protobuf messages to dictionaries when parsing gRPC responses.
This is a significant CPU bottleneck when processing large numbers of
vectors, as reported in PR #537 where users experienced ~100
vectors/second throughput.
The `MessageToDict` conversion is expensive because it:
1. Serializes the entire protobuf message to JSON
2. Deserializes it back into a Python dictionary
3. Does this for every field, even when we only need specific fields
Additionally, several other performance issues were identified:
- Metadata conversion using `MessageToDict` on `Struct` messages
- Inefficient list construction (append vs pre-allocation)
- Unnecessary dict creation for `SparseValues` parsing
- Response header processing overhead
## Solution
Optimized all gRPC response parsing functions in
`pinecone/grpc/utils.py` to directly access protobuf fields instead of
converting entire messages to dictionaries. This approach:
1. **Directly accesses protobuf fields**: Uses `response.vectors`,
`response.matches`, `response.namespace`, etc. directly
2. **Optimized metadata conversion**: Created `_struct_to_dict()` helper
that directly accesses `Struct` fields (~1.5-2x faster than
`MessageToDict`)
3. **Pre-allocates lists**: Uses `[None] * len()` for known-size lists
(~6.5% improvement)
4. **Direct SparseValues creation**: Creates `SparseValues` objects
directly instead of going through dict conversion (~410x faster)
5. **Caches protobuf attributes**: Stores repeated attribute accesses in
local variables
6. **Optimized response info extraction**: Improved
`extract_response_info()` performance with module-level constants and
early returns
7. **Maintains backward compatibility**: Output format remains identical
to the previous implementation
## Performance Impact
Performance testing of the response parsing functions show significant
improvements across all optimized functions.
## Changes
### Modified Files
- `pinecone/grpc/utils.py`: Optimized 9 response parsing functions with
direct protobuf field access
- Added `_struct_to_dict()` helper for optimized metadata conversion
(~1.5-2x faster)
- Pre-allocated lists where size is known (~6.5% improvement)
- Direct `SparseValues` creation (removed dict conversion overhead)
- Cached protobuf message attributes
- Removed dead code paths (dict fallback in `parse_usage`)
- `pinecone/grpc/index_grpc.py`: Updated to pass protobuf messages
directly to parse functions
- `pinecone/grpc/resources/vector_grpc.py`: Updated to pass protobuf
messages directly to parse functions
- `pinecone/utils/response_info.py`: Optimized `extract_response_info()`
with module-level constants and early returns
- `tests/perf/test_fetch_response_optimization.py`: New performance
tests for fetch response parsing
- `tests/perf/test_query_response_optimization.py`: New performance
tests for query response parsing
- `tests/perf/test_other_parse_methods.py`: New performance tests for
all other parse methods
- `tests/perf/test_grpc_parsing_perf.py`: Extended with additional
benchmarks
### Technical Details
**Core Optimizations**:
1. **`_struct_to_dict()` Helper Function**:
- Directly accesses protobuf `Struct` and `Value` fields
- Handles all value types (null, number, string, bool, struct, list)
- Recursively processes nested structures
- ~1.5-2x faster than `json_format.MessageToDict` for metadata
conversion
2. **List Pre-allocation**:
- `parse_query_response`: Pre-allocates `matches` list with `[None] *
len(matches_proto)`
- `parse_list_namespaces_response`: Pre-allocates `namespaces` list
- ~6.5% performance improvement over append-based construction
3. **Direct SparseValues Creation**:
- Replaced `parse_sparse_values(dict)` with direct
`SparseValues(indices=..., values=...)` creation
- ~410x faster (avoids dict creation and conversion overhead)
## Testing
- All existing unit tests pass (224 tests in `tests/unit_grpc`)
- Comprehensive pytest benchmark tests added for all optimized
functions:
- `test_fetch_response_optimization.py`: Tests for fetch response with
varying metadata sizes
- `test_query_response_optimization.py`: Tests for query response with
varying match counts, dimensions, metadata sizes, and sparse vectors
- `test_other_parse_methods.py`: Tests for all other parse methods
(fetch_by_metadata, list_namespaces, stats, upsert, update,
namespace_description)
- Mypy type checking passes with and without grpc extras (with types
extras)
- No breaking changes - output format remains identical
## Related
This addresses the performance issue reported in PR #537, implementing a
similar optimization approach but adapted for the current codebase
structure. All parse methods have been optimized with comprehensive
performance testing to verify improvements.1 parent 53082f1 commit 27e751c
File tree
9 files changed
+1389
-173
lines changed- pinecone
- grpc
- resources
- utils
- tests/perf
9 files changed
+1389
-173
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
7 | 6 | | |
8 | 7 | | |
9 | 8 | | |
| |||
15 | 14 | | |
16 | 15 | | |
17 | 16 | | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| |||
501 | 502 | | |
502 | 503 | | |
503 | 504 | | |
504 | | - | |
| 505 | + | |
505 | 506 | | |
506 | | - | |
| 507 | + | |
507 | 508 | | |
508 | 509 | | |
509 | 510 | | |
510 | | - | |
| 511 | + | |
511 | 512 | | |
512 | 513 | | |
513 | 514 | | |
| |||
535 | 536 | | |
536 | 537 | | |
537 | 538 | | |
538 | | - | |
| 539 | + | |
539 | 540 | | |
540 | 541 | | |
541 | 542 | | |
| |||
626 | 627 | | |
627 | 628 | | |
628 | 629 | | |
629 | | - | |
630 | | - | |
| 630 | + | |
| 631 | + | |
631 | 632 | | |
632 | 633 | | |
633 | 634 | | |
| |||
640 | 641 | | |
641 | 642 | | |
642 | 643 | | |
643 | | - | |
| 644 | + | |
644 | 645 | | |
645 | 646 | | |
646 | 647 | | |
| |||
681 | 682 | | |
682 | 683 | | |
683 | 684 | | |
684 | | - | |
685 | | - | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
686 | 688 | | |
687 | 689 | | |
688 | 690 | | |
| |||
946 | 948 | | |
947 | 949 | | |
948 | 950 | | |
949 | | - | |
950 | | - | |
| 951 | + | |
951 | 952 | | |
952 | 953 | | |
953 | 954 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
7 | 6 | | |
8 | 7 | | |
9 | 8 | | |
| |||
13 | 12 | | |
14 | 13 | | |
15 | 14 | | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| |||
444 | 445 | | |
445 | 446 | | |
446 | 447 | | |
447 | | - | |
| 448 | + | |
448 | 449 | | |
449 | | - | |
| 450 | + | |
450 | 451 | | |
451 | 452 | | |
452 | 453 | | |
453 | | - | |
| 454 | + | |
454 | 455 | | |
455 | 456 | | |
456 | 457 | | |
| |||
478 | 479 | | |
479 | 480 | | |
480 | 481 | | |
481 | | - | |
| 482 | + | |
482 | 483 | | |
483 | 484 | | |
484 | 485 | | |
| |||
569 | 570 | | |
570 | 571 | | |
571 | 572 | | |
572 | | - | |
573 | | - | |
| 573 | + | |
| 574 | + | |
574 | 575 | | |
575 | 576 | | |
576 | 577 | | |
| |||
583 | 584 | | |
584 | 585 | | |
585 | 586 | | |
586 | | - | |
| 587 | + | |
587 | 588 | | |
588 | 589 | | |
589 | 590 | | |
| |||
658 | 659 | | |
659 | 660 | | |
660 | 661 | | |
661 | | - | |
662 | | - | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
663 | 665 | | |
664 | 666 | | |
665 | 667 | | |
| |||
853 | 855 | | |
854 | 856 | | |
855 | 857 | | |
856 | | - | |
857 | | - | |
| 858 | + | |
0 commit comments