Fix slow handling of protobuf comments (#955)

This syncs cl/613879585 and cl/614589429.

Closes #935.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index 1876f2a..06488bb 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -13,6 +13,8 @@
   and values are now handled to generate Dart `@deprecated` annotations.
   ([#900], [#908])
 * `protoc_plugin` and generated files now require Dart 3.3.0. (#953)
+* Fix performance issues when handling documentation comments in protobufs.
+  ([#935], [#955])
 
 [#738]: https://github.com/google/protobuf.dart/issues/738
 [#903]: https://github.com/google/protobuf.dart/pull/903
@@ -22,6 +24,8 @@
 [#909]: https://github.com/google/protobuf.dart/pull/909
 [#908]: https://github.com/google/protobuf.dart/pull/908
 [#953]: https://github.com/google/protobuf.dart/pull/953
+[#935]: https://github.com/google/protobuf.dart/pull/935
+[#955]: https://github.com/google/protobuf.dart/pull/955
 
 ## 21.1.2
 
diff --git a/protoc_plugin/lib/src/shared.dart b/protoc_plugin/lib/src/shared.dart
index 8fe66bb..7ecdb01 100644
--- a/protoc_plugin/lib/src/shared.dart
+++ b/protoc_plugin/lib/src/shared.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import '../protoc.dart';
+import '../src/generated/descriptor.pb.dart';
 
 const protobufImportPrefix = r'$pb';
 const asyncImportPrefix = r'$async';
@@ -14,6 +15,20 @@
 const mixinImportPrefix = r'$mixin';
 
 extension FileDescriptorProtoExt on FileGenerator {
+  bool _listEquals(List<int> a, List<int> b) {
+    if (a.length != b.length) {
+      return false;
+    }
+    // Note: paths are much likely to share common prefixes than to share common
+    // suffixes, so it's probably faster to run this loop backwards ;)
+    for (var i = a.length - 1; i >= 0; i--) {
+      if (a[i] != b[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   /// Convert leading comments of a definition at [path] to Dart doc comment
   /// syntax.
   ///
@@ -23,18 +38,22 @@
   /// The output can contain multiple lines. None of the lines will have
   /// trailing whitespace.
   String? commentBlock(List<int> path) {
-    final bits = descriptor.sourceCodeInfo.location
-        .where((element) => element.path.toString() == path.toString())
-        .toList();
-
-    if (bits.length == 1) {
-      final match = bits.single;
-      return toDartComment(match.leadingComments);
+    SourceCodeInfo_Location? singleMatch;
+    for (final location in descriptor.sourceCodeInfo.location) {
+      if (_listEquals(location.path, path)) {
+        if (singleMatch == null) {
+          singleMatch = location;
+        } else {
+          // TODO: evaluate if we should just concatenate all of the matching
+          // entries.
+          stderr.writeln('Too many source code locations. Skipping.');
+          return null;
+        }
+      }
     }
 
-    if (bits.length > 1) {
-      // TODO: evaluate if we should just concatenate all of the entries.
-      stderr.writeln('Too many source code locations. Skipping.');
+    if (singleMatch != null) {
+      return toDartComment(singleMatch.leadingComments);
     }
 
     return null;