mirror of
https://github.com/protocolbuffers/protobuf
synced 2026-08-26 02:23:14 -04:00
Fix for performance penalty in deep dependencies (#20392)
The goal of this PR is to fix #20371 (closed) #24267 and #24330 and improve the performance of csharp runtime when dealing with deep nested dependencies of .proto files.
- The fix consists in caching the recursive calls for the search of extensions.
- Two identical datasets (nested proto files) are created automatically using Bazel: one to test it with caching enabled, and another one to test it with no caching. Note: the same dataset cannot be used because once it is loaded inside the test, it cannot be unloaded.
- The created datasets have a width of 6 and a depth of 6, enough to showcase the dependency impact and to not impact the testing time for unit tests. This could be configured.
- In the dataset there is only one proto file with a message `Example` whose descriptor is loaded, and by doing so all the dependencies (and sub dependencies) that it has.
- A set of benchmark metrics have been created to evaluate the performance impact before and after the fix.
- The assertion inside the unit test makes sure that when using metrics the number of traversed extensions (dependencies of the dependencies) when using caching, is reduced by a factor of 1000x.
- The second assertion also makes sure that the time used to load the descriptor when using cache is less than the one without using cache.
Closes #20392
COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/20392 from fgarciacorona:fix_20371_csharp_deep_dependencies b1353f6fd1
PiperOrigin-RevId: 919713760
This commit is contained in:
parent
3981478dda
commit
aeb0dc2f97
7 changed files with 482 additions and 6 deletions
4
csharp/.gitignore
vendored
4
csharp/.gitignore
vendored
|
|
@ -34,6 +34,10 @@ lib/protoc.exe
|
|||
# Benchmark output
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# Auto generated csharp classes
|
||||
UnittestDeepDependenciesCached/
|
||||
UnittestDeepDependenciesNotCached/
|
||||
|
||||
# Reinstate generated code for test files
|
||||
!any_test.pb.*
|
||||
!map*unittest.pb.*
|
||||
|
|
|
|||
|
|
@ -89,6 +89,30 @@ $PROTOC -Isrc -I. -Ijava/core/src/main/resources/ \
|
|||
src/google/protobuf/unittest_proto3_optional.proto \
|
||||
src/google/protobuf/unittest_retention.proto
|
||||
|
||||
# We can safely ignore the unused import warning as the
|
||||
# purpose of the test is to work with the dependencies
|
||||
# Deep dependencies test protos - Cached version
|
||||
# Proto files are generated by Bazel: bazel build //csharp/protos/unittest_deep_dependencies:generate_cached_protos
|
||||
mkdir -p csharp/src/Google.Protobuf.Test.TestProtos/UnittestDeepDependenciesCached
|
||||
$PROTOC --proto_path=bazel-bin/csharp/protos/unittest_deep_dependencies \
|
||||
--experimental_allow_proto3_optional \
|
||||
--experimental_editions \
|
||||
--csharp_out=csharp/src/Google.Protobuf.Test.TestProtos/UnittestDeepDependenciesCached \
|
||||
--csharp_opt=file_extension=.pb.cs \
|
||||
--csharp_opt=base_namespace=UnittestDeepDependenciesCached \
|
||||
$(find bazel-bin/csharp/protos/unittest_deep_dependencies/unittest_deep_dependencies_cached -name "*.proto")
|
||||
|
||||
# Deep dependencies test protos - NotCached version
|
||||
# Proto files are generated by Bazel: bazel build //csharp/protos/unittest_deep_dependencies:generate_notcached_protos
|
||||
mkdir -p csharp/src/Google.Protobuf.Test.TestProtos/UnittestDeepDependenciesNotCached
|
||||
$PROTOC --proto_path=bazel-bin/csharp/protos/unittest_deep_dependencies \
|
||||
--experimental_allow_proto3_optional \
|
||||
--experimental_editions \
|
||||
--csharp_out=csharp/src/Google.Protobuf.Test.TestProtos/UnittestDeepDependenciesNotCached \
|
||||
--csharp_opt=file_extension=.pb.cs \
|
||||
--csharp_opt=base_namespace=UnittestDeepDependenciesNotCached \
|
||||
$(find bazel-bin/csharp/protos/unittest_deep_dependencies/unittest_deep_dependencies_not_cached -name "*.proto")
|
||||
|
||||
# AddressBook sample protos
|
||||
$PROTOC -Iexamples -Isrc --csharp_out=csharp/src/AddressBook \
|
||||
--csharp_opt=file_extension=.pb.cs \
|
||||
|
|
|
|||
35
csharp/protos/unittest_deep_dependencies/BUILD.bazel
Normal file
35
csharp/protos/unittest_deep_dependencies/BUILD.bazel
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Exponential dependency proto structure for testing deep dependencies.
|
||||
Width=6, Depth=6, creating 6^6=46,656 dependency paths.
|
||||
|
||||
This generates two variants:
|
||||
- Cached version: WITH caching
|
||||
- NotCached version: WITHOUT caching
|
||||
|
||||
Structure:
|
||||
- Level 0: 1 base file
|
||||
- Level 1: 6 files, each importing Level 0
|
||||
- Levels 2-5: 6 files per level, each importing ALL 6 files from previous level
|
||||
- Level 6: 1 final file importing all 6 Level 5 files
|
||||
- Entry point: Example/ExampleNotCached message importing the Level 6 file
|
||||
|
||||
NOTE: This build file is designed for MODULE.bazel (bzlmod).
|
||||
Use:
|
||||
bazel build //csharp/protos/unittest_deep_dependencies:generate_cached_protos
|
||||
bazel build //csharp/protos/unittest_deep_dependencies:generate_notcached_protos
|
||||
"""
|
||||
|
||||
load(":generate_protos.bzl", "generate_deep_dependencies_protos")
|
||||
|
||||
package(default_visibility = ["//csharp:__subpackages__"])
|
||||
|
||||
# Configuration
|
||||
WIDTH = 6
|
||||
|
||||
DEPTH = 6
|
||||
|
||||
# Generate both cached and notcached variants
|
||||
generate_deep_dependencies_protos(
|
||||
depth = DEPTH,
|
||||
width = WIDTH,
|
||||
)
|
||||
256
csharp/protos/unittest_deep_dependencies/generate_protos.bzl
Normal file
256
csharp/protos/unittest_deep_dependencies/generate_protos.bzl
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
"""
|
||||
Macro to generate exponential dependency proto structure for both cached and notcached variants.
|
||||
Generates proto files into separate subdirectories without duplication.
|
||||
"""
|
||||
|
||||
load("//bazel:proto_library.bzl", "proto_library")
|
||||
|
||||
def _pad(n):
|
||||
"""Zero-pad a number to 2 digits."""
|
||||
return str(n) if n >= 10 else "0" + str(n)
|
||||
|
||||
def generate_deep_dependencies_protos(width, depth):
|
||||
"""
|
||||
Generate proto files and proto_library targets for both cached and notcached variants.
|
||||
|
||||
Args:
|
||||
width: Number of files per level (except last level which has 1)
|
||||
depth: Number of levels
|
||||
"""
|
||||
|
||||
# Generate both variants
|
||||
_generate_variant("cached", width, depth, "unittest_deep_dependencies_cached")
|
||||
_generate_variant("notcached", width, depth, "unittest_deep_dependencies_not_cached")
|
||||
|
||||
def _generate_variant(variant, width, depth, package):
|
||||
"""
|
||||
Generate proto files and libraries for a specific variant.
|
||||
|
||||
Args:
|
||||
variant: "cached" or "notcached"
|
||||
width: Number of files per level
|
||||
depth: Number of levels
|
||||
package: Proto package name
|
||||
"""
|
||||
|
||||
# Calculate all proto file names - use package name for the directory
|
||||
all_files = [package + "/file_00_00.proto"]
|
||||
|
||||
for level in range(1, depth + 1):
|
||||
num_files = width if level < depth else 1
|
||||
for idx in range(num_files):
|
||||
all_files.append(package + "/file_" + _pad(level) + "_" + _pad(idx) + ".proto")
|
||||
|
||||
all_files.append(package + "/file_entry.proto")
|
||||
|
||||
# Single genrule to generate ALL proto files for this variant
|
||||
_generate_all_protos(
|
||||
name = "generate_" + variant + "_protos",
|
||||
variant = variant,
|
||||
width = width,
|
||||
depth = depth,
|
||||
package = package,
|
||||
all_files = all_files,
|
||||
)
|
||||
|
||||
# Level 0: Base proto_library
|
||||
proto_library(
|
||||
name = variant + "_file_00_00_proto",
|
||||
srcs = [":" + package + "/file_00_00.proto"],
|
||||
strip_import_prefix = "/third_party/protobuf/csharp/protos/unittest_deep_dependencies",
|
||||
)
|
||||
|
||||
# Level 1: WIDTH proto_library targets
|
||||
for i in range(width):
|
||||
i_pad = _pad(i)
|
||||
proto_library(
|
||||
name = variant + "_file_01_" + i_pad + "_proto",
|
||||
srcs = [":" + package + "/file_01_" + i_pad + ".proto"],
|
||||
strip_import_prefix = "/third_party/protobuf/csharp/protos/unittest_deep_dependencies",
|
||||
deps = [":" + variant + "_file_00_00_proto"],
|
||||
)
|
||||
|
||||
# Levels 2 through DEPTH
|
||||
for level in range(2, depth + 1):
|
||||
num_files = width if level < depth else 1
|
||||
level_pad = _pad(level)
|
||||
level_prev_pad = _pad(level - 1)
|
||||
|
||||
for idx in range(num_files):
|
||||
idx_pad = _pad(idx)
|
||||
deps = [
|
||||
":" + variant + "_file_" + level_prev_pad + "_" + _pad(prev_idx) + "_proto"
|
||||
for prev_idx in range(width)
|
||||
]
|
||||
|
||||
proto_library(
|
||||
name = variant + "_file_" + level_pad + "_" + idx_pad + "_proto",
|
||||
srcs = [":" + package + "/file_" + level_pad + "_" + idx_pad + ".proto"],
|
||||
strip_import_prefix = "/third_party/protobuf/csharp/protos/unittest_deep_dependencies",
|
||||
deps = deps,
|
||||
)
|
||||
|
||||
# Entry point proto_library
|
||||
proto_library(
|
||||
name = variant + "_entry_proto",
|
||||
srcs = [":" + package + "/file_entry.proto"],
|
||||
strip_import_prefix = "/third_party/protobuf/csharp/protos/unittest_deep_dependencies",
|
||||
deps = [":" + variant + "_file_" + _pad(depth) + "_00_proto"],
|
||||
)
|
||||
|
||||
# Generate C# source code (.pb.cs) from these generated .proto files
|
||||
_generate_csharp_sources(
|
||||
name = variant + "_csharp_gen",
|
||||
variant = variant,
|
||||
all_files = all_files,
|
||||
)
|
||||
|
||||
def _generate_all_protos(name, variant, width, depth, package, all_files):
|
||||
"""Generate a single genrule that creates all proto files for a variant.
|
||||
|
||||
These files only contain imports to create deep dependency chains.
|
||||
Only the entry file has an actual message.
|
||||
"""
|
||||
|
||||
# Build the command to generate all files
|
||||
cmd_parts = []
|
||||
|
||||
if variant == "cached":
|
||||
# Cached version - Level 0 with a message
|
||||
cmd_parts.append("""
|
||||
mkdir -p $(RULEDIR)/%s
|
||||
cat > $(RULEDIR)/%s/file_00_00.proto << 'EOFPROTO'
|
||||
syntax = "proto3";
|
||||
|
||||
package %s;
|
||||
|
||||
message Level00Message {
|
||||
string field = 1;
|
||||
}
|
||||
EOFPROTO
|
||||
""" % (package, package, package))
|
||||
|
||||
# Level 1 files - with messages
|
||||
for i in range(width):
|
||||
i_pad = _pad(i)
|
||||
cmd_parts.append("""
|
||||
cat > $(RULEDIR)/%s/file_01_%s.proto << 'EOFPROTO'
|
||||
syntax = "proto3";
|
||||
|
||||
package %s;
|
||||
|
||||
import "%s/file_00_00.proto";
|
||||
|
||||
message Level01Message%s {
|
||||
Level00Message field = 1;
|
||||
}
|
||||
EOFPROTO
|
||||
""" % (package, i_pad, package, package, i_pad))
|
||||
else:
|
||||
# Notcached version - Level 0 empty
|
||||
cmd_parts.append("""
|
||||
mkdir -p $(RULEDIR)/%s
|
||||
cat > $(RULEDIR)/%s/file_00_00.proto << 'EOFPROTO'
|
||||
syntax = "proto3";
|
||||
|
||||
package %s;
|
||||
EOFPROTO
|
||||
""" % (package, package, package))
|
||||
|
||||
# Level 1 files - just imports
|
||||
for i in range(width):
|
||||
i_pad = _pad(i)
|
||||
cmd_parts.append("""
|
||||
cat > $(RULEDIR)/%s/file_01_%s.proto << 'EOFPROTO'
|
||||
syntax = "proto3";
|
||||
|
||||
package %s;
|
||||
|
||||
import "%s/file_00_00.proto";
|
||||
EOFPROTO
|
||||
""" % (package, i_pad, package, package))
|
||||
|
||||
# Levels 2 through DEPTH - just imports, no messages
|
||||
for level in range(2, depth + 1):
|
||||
num_files = width if level < depth else 1
|
||||
level_pad = _pad(level)
|
||||
level_prev_pad = _pad(level - 1)
|
||||
|
||||
for idx in range(num_files):
|
||||
idx_pad = _pad(idx)
|
||||
|
||||
import_lines = []
|
||||
for prev_idx in range(width):
|
||||
import_lines.append('import "%s/file_%s_%s.proto";' % (package, level_prev_pad, _pad(prev_idx)))
|
||||
imports_str = "\n".join(import_lines)
|
||||
|
||||
cmd_parts.append("""
|
||||
cat > $(RULEDIR)/%s/file_%s_%s.proto << 'EOFPROTO'
|
||||
syntax = "proto3";
|
||||
|
||||
package %s;
|
||||
|
||||
%s
|
||||
EOFPROTO
|
||||
""" % (package, level_pad, idx_pad, package, imports_str))
|
||||
|
||||
# Entry point file
|
||||
if variant == "cached":
|
||||
message_type = "Example"
|
||||
else:
|
||||
message_type = "ExampleNotCached"
|
||||
|
||||
cmd_parts.append("""
|
||||
cat > $(RULEDIR)/%s/file_entry.proto << 'EOFPROTO'
|
||||
syntax = "proto3";
|
||||
|
||||
package %s;
|
||||
|
||||
import "%s/file_%s_00.proto";
|
||||
|
||||
message %s {
|
||||
string test_field = 1;
|
||||
}
|
||||
EOFPROTO
|
||||
""" % (package, package, package, _pad(depth), message_type))
|
||||
|
||||
native.genrule(
|
||||
name = name,
|
||||
outs = all_files,
|
||||
cmd = "".join(cmd_parts),
|
||||
)
|
||||
|
||||
def _generate_csharp_sources(name, variant, all_files):
|
||||
"""Generate C# source files from generated proto files using a single genrule."""
|
||||
|
||||
base_namespace = "UnittestDeepDependenciesCached" if variant == "cached" else "UnittestDeepDependenciesNotCached"
|
||||
|
||||
# Translate each proto file path to its expected C# output file name
|
||||
csharp_outputs = []
|
||||
for f in all_files:
|
||||
basename = f.split("/")[-1] # "file_00_00.proto"
|
||||
name_no_ext = basename.split(".proto")[0] # "file_00_00"
|
||||
csharp_name = ""
|
||||
for part in name_no_ext.split("_"):
|
||||
if len(part) > 0:
|
||||
csharp_name += part[0].upper() + part[1:]
|
||||
|
||||
csharp_outputs.append(variant + "_cs/" + csharp_name + ".pb.cs")
|
||||
|
||||
# Invoke protoc hermetically with the correct base namespace and include path
|
||||
native.genrule(
|
||||
name = name,
|
||||
testonly = True,
|
||||
srcs = [":" + f for f in all_files],
|
||||
outs = csharp_outputs,
|
||||
cmd = (
|
||||
"$(location //net/proto2/compiler/public:protocol_compiler) " +
|
||||
"--csharp_out=$(RULEDIR)/" + variant + "_cs " +
|
||||
"--csharp_opt=file_extension=.pb.cs " +
|
||||
"--csharp_opt=base_namespace=" + base_namespace + " " +
|
||||
"-I$$(dirname $$(dirname $(location :" + all_files[0] + "))) " +
|
||||
"$(SRCS)"
|
||||
),
|
||||
tools = ["//net/proto2/compiler/public:protocol_compiler"],
|
||||
visibility = ["//src/google/protobuf/csharp:__subpackages__"],
|
||||
)
|
||||
|
|
@ -13,7 +13,9 @@ using NUnit.Framework;
|
|||
using ProtobufUnittest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnitTest.Issues.TestProtos;
|
||||
using static Google.Protobuf.Reflection.FeatureSet.Types;
|
||||
using proto2 = Google.Protobuf.TestProtos.Proto2;
|
||||
|
|
@ -35,6 +37,67 @@ namespace Google.Protobuf.Reflection
|
|||
UnittestImportPublicProto3Reflection.Descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performance test comparing descriptor loading WITH vs WITHOUT extension caching.
|
||||
///
|
||||
/// This test uses TWO separate proto sets to measure both scenarios in a single execution:
|
||||
/// - unittest_deep_dependencies_cached: Example message (WITH caching - default)
|
||||
/// - unittest_deep_dependencies_notcached: ExampleNotcached message (WITHOUT caching)
|
||||
///
|
||||
/// Both proto sets have identical structure (WIDTH=6, DEPTH=6, 33 files total) but different
|
||||
/// package names, allowing accurate measurement of caching performance in one test run.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void FileDescriptor_ExtensionCachingPerformance()
|
||||
{
|
||||
// ========== Part 1: Test WITH caching (default behavior) ==========
|
||||
#if DEBUG
|
||||
FileDescriptor.ResetCounters();
|
||||
#endif
|
||||
|
||||
var stopwatchWithCache = Stopwatch.StartNew();
|
||||
// Access the Descriptor property to trigger static initialization if not already done.
|
||||
// The first access to any member of the generated Reflection class triggers the static
|
||||
// constructor which calls FileDescriptor.FromGeneratedCode(), and that's when traversal happens.
|
||||
var descriptor = UnittestDeepDependenciesCached.Example.Descriptor;
|
||||
var exampleWithCache = new UnittestDeepDependenciesCached.Example();
|
||||
stopwatchWithCache.Stop();
|
||||
|
||||
#if DEBUG
|
||||
var traversalsWithCache = FileDescriptor.GetAllDependedExtensionsCount;
|
||||
|
||||
// Verify caching is working: should have minimal redundant traversals
|
||||
Assert.Less(traversalsWithCache, 100,
|
||||
$"Should have very few redundant traversals with cache, but got {traversalsWithCache}");
|
||||
#endif
|
||||
|
||||
// ========== Part 2: Test WITHOUT caching ==========
|
||||
FileDescriptor.DisableExtensionCaching();
|
||||
#if DEBUG
|
||||
FileDescriptor.ResetCounters();
|
||||
#endif
|
||||
|
||||
var stopwatchNoCache = Stopwatch.StartNew();
|
||||
// Force descriptor initialization by accessing it - this is when extension traversal happens
|
||||
var descriptorNoCache = UnittestDeepDependenciesNotCached.ExampleNotCached.Descriptor;
|
||||
var exampleNoCache = new UnittestDeepDependenciesNotCached.ExampleNotCached();
|
||||
stopwatchNoCache.Stop();
|
||||
|
||||
#if DEBUG
|
||||
var traversalsWithoutCache = FileDescriptor.GetAllDependedExtensionsCount;
|
||||
|
||||
// Verify the problem: massive redundant traversals
|
||||
Assert.Greater(traversalsWithoutCache, 8000,
|
||||
$"Should have thousands of redundant traversals without cache, but got {traversalsWithoutCache}");
|
||||
|
||||
var improvement = (double) traversalsWithoutCache / Math.Max(1, traversalsWithCache);
|
||||
|
||||
Assert.Greater(improvement, 1000, $"Traversals Improvement = WITHOUT cache: {traversalsWithoutCache:N0} / WITH cache: {traversalsWithCache:N0} = {improvement:F0}x should be > 1000x !");
|
||||
#endif
|
||||
Assert.Greater(stopwatchNoCache.Elapsed, stopwatchWithCache.Elapsed, $"Traversals time: WITHOUT cache: {stopwatchNoCache.Elapsed} should be > WITH cache: {stopwatchWithCache.Elapsed} !");
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FileDescriptor_BuildFromByteStrings()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,11 +7,16 @@
|
|||
// https://developers.google.com/open-source/licenses/bsd
|
||||
#endregion
|
||||
|
||||
#if DEBUG
|
||||
#define WITH_BENCHMARKING
|
||||
#endif
|
||||
|
||||
using Google.Protobuf.Collections;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
|
||||
|
|
@ -63,6 +68,62 @@ namespace Google.Protobuf.Reflection
|
|||
|
||||
private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations;
|
||||
|
||||
private static readonly Dictionary<string, List<Extension>> allDependedExtensionsCache = new();
|
||||
private static bool extensionCachingEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Disables caching of discovered extensions. Greatly reduces performance in highly nested protos but lowers the memory usage.
|
||||
/// </summary>
|
||||
public static void DisableExtensionCaching()
|
||||
{
|
||||
extensionCachingEnabled = false;
|
||||
}
|
||||
|
||||
#if WITH_BENCHMARKING
|
||||
|
||||
/// <summary>
|
||||
/// Resets the benchmarking counters.
|
||||
/// </summary>
|
||||
public static void ResetCounters()
|
||||
{
|
||||
GetAllExtensionsCount = 0;
|
||||
TotalReturnedExtensionsCount = 0;
|
||||
GetAllGeneratedExtensionsCount = 0;
|
||||
GetAllDependedExtensionsCount = 0;
|
||||
GetAllDependedExtensionsFromMessageCount = 0;
|
||||
}
|
||||
|
||||
internal static long GetAllExtensionsCount { get; private set; }
|
||||
internal static long TotalReturnedExtensionsCount { get; private set;}
|
||||
internal static long GetAllGeneratedExtensionsCount { get; private set; }
|
||||
internal static long GetAllDependedExtensionsCount { get; private set; }
|
||||
internal static long GetAllDependedExtensionsFromMessageCount { get; private set; }
|
||||
|
||||
internal static TextWriter WriteBenchmark(TextWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
return writer;
|
||||
}
|
||||
|
||||
writer.WriteLine($"GetAllExtensionsCount: {GetAllExtensionsCount}");
|
||||
writer.WriteLine($"GetAllGeneratedExtensionsCount: {GetAllGeneratedExtensionsCount}");
|
||||
writer.WriteLine($"GetAllDependedExtensionsCount: {GetAllDependedExtensionsCount}");
|
||||
writer.WriteLine($"GetAllDependedExtensionsFromMessageCount: {GetAllDependedExtensionsFromMessageCount}");
|
||||
writer.WriteLine($"TotalReturnedExtensionsCount: {TotalReturnedExtensionsCount}");
|
||||
|
||||
return writer;
|
||||
}
|
||||
#else
|
||||
internal static TextWriter WriteBenchmark(TextWriter writer)
|
||||
{
|
||||
writer?.WriteLine("Benchmarking is disabled");
|
||||
|
||||
return writer;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IList<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
|
||||
{
|
||||
SerializedData = descriptorData;
|
||||
|
|
@ -417,7 +478,7 @@ namespace Google.Protobuf.Reflection
|
|||
FileDescriptor[] dependencies,
|
||||
GeneratedClrTypeInfo generatedCodeInfo)
|
||||
{
|
||||
ExtensionRegistry registry = new ExtensionRegistry();
|
||||
ExtensionRegistry registry = new();
|
||||
registry.AddRange(GetAllExtensions(dependencies, generatedCodeInfo));
|
||||
|
||||
FileDescriptorProto proto;
|
||||
|
|
@ -444,29 +505,60 @@ namespace Google.Protobuf.Reflection
|
|||
|
||||
private static IEnumerable<Extension> GetAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo)
|
||||
{
|
||||
return dependencies.SelectMany(GetAllDependedExtensions).Distinct(ExtensionRegistry.ExtensionComparer.Instance).Concat(GetAllGeneratedExtensions(generatedInfo));
|
||||
#if WITH_BENCHMARKING
|
||||
GetAllExtensionsCount++;
|
||||
#endif
|
||||
|
||||
var allExtensions = dependencies.SelectMany(GetAllDependedExtensions).Distinct(ExtensionRegistry.ExtensionComparer.Instance).Concat(GetAllGeneratedExtensions(generatedInfo)).ToList();
|
||||
|
||||
#if WITH_BENCHMARKING
|
||||
TotalReturnedExtensionsCount += allExtensions.Count;
|
||||
#endif
|
||||
|
||||
return allExtensions;
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)
|
||||
{
|
||||
return generated.Extensions.Concat(generated.NestedTypes.Where(t => t != null).SelectMany(GetAllGeneratedExtensions));
|
||||
#if WITH_BENCHMARKING
|
||||
GetAllGeneratedExtensionsCount++;
|
||||
#endif
|
||||
return generated.Extensions.Concat(generated.NestedTypes.Where(t => t != null).SelectMany(GetAllGeneratedExtensions)).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllDependedExtensions(FileDescriptor descriptor)
|
||||
{
|
||||
return descriptor.Extensions.UnorderedExtensions
|
||||
if (extensionCachingEnabled && allDependedExtensionsCache.TryGetValue(descriptor.Name, out List<Extension> cachedExtensions))
|
||||
{
|
||||
return cachedExtensions;
|
||||
}
|
||||
|
||||
#if WITH_BENCHMARKING
|
||||
GetAllDependedExtensionsCount++;
|
||||
#endif
|
||||
var extensions = descriptor.Extensions.UnorderedExtensions
|
||||
.Select(s => s.Extension)
|
||||
.Where(e => e != null)
|
||||
.Concat(descriptor.Dependencies.Concat(descriptor.PublicDependencies).SelectMany(GetAllDependedExtensions))
|
||||
.Concat(descriptor.MessageTypes.SelectMany(GetAllDependedExtensionsFromMessage));
|
||||
.Concat(descriptor.MessageTypes.SelectMany(GetAllDependedExtensionsFromMessage)).ToList();
|
||||
|
||||
if (extensionCachingEnabled)
|
||||
{
|
||||
allDependedExtensionsCache[descriptor.Name] = extensions;
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private static IEnumerable<Extension> GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor)
|
||||
{
|
||||
#if WITH_BENCHMARKING
|
||||
GetAllDependedExtensionsFromMessageCount++;
|
||||
#endif
|
||||
return descriptor.Extensions.UnorderedExtensions
|
||||
.Select(s => s.Extension)
|
||||
.Where(e => e != null)
|
||||
.Concat(descriptor.NestedTypes.SelectMany(GetAllDependedExtensionsFromMessage));
|
||||
.Concat(descriptor.NestedTypes.SelectMany(GetAllDependedExtensionsFromMessage)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ done
|
|||
# shell script that generates everything required. The output files are stable,
|
||||
# so just regenerating in place should be harmless.
|
||||
${BazelBin} build src/google/protobuf/compiler:protoc "$@"
|
||||
${BazelBin} build //csharp/protos/unittest_deep_dependencies:generate_cached_protos "$@"
|
||||
${BazelBin} build //csharp/protos/unittest_deep_dependencies:generate_notcached_protos "$@"
|
||||
(export PROTOC=$PWD/bazel-bin/protoc && cd csharp && ./generate_protos.sh)
|
||||
|
||||
echo "::endgroup::"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue