Cache last-seen value to `GetClassData()` (per-thread)

PiperOrigin-RevId: 778180192
diff --git a/src/google/protobuf/message_lite.h b/src/google/protobuf/message_lite.h
index 25264ef..82331ad 100644
--- a/src/google/protobuf/message_lite.h
+++ b/src/google/protobuf/message_lite.h
@@ -30,6 +30,7 @@
 #include <memory>
 #include <string>
 #include <type_traits>
+#include <typeinfo>
 #include <utility>
 
 #include "absl/base/attributes.h"
@@ -1226,8 +1227,28 @@
 PROTOBUF_NDEBUG_INLINE const ClassData* GetClassData(const T& msg) {
   static_assert(std::is_base_of_v<MessageLite, T>);
   if constexpr (std::is_same_v<T, MessageLite> || std::is_same_v<Message, T>) {
-    PROTOBUF_DEBUG_COUNTER("GetClassData.Virtual").Inc();
+#if PROTOBUF_RTTI && !defined(PROTOBUF_CUSTOM_VTABLE)
+    // Cache the last seen `ClassData` to avoid the virtual call.
+    static thread_local const std::type_info* last_type_info = nullptr;
+    static thread_local const ClassData* last_class_data = nullptr;
+    if (&typeid(msg) == last_type_info) {
+      PROTOBUF_DEBUG_COUNTER("GetClassData.Cached").Inc();
+      ABSL_DCHECK_EQ(last_class_data, msg.GetClassData());
+      return last_class_data;
+    } else {
+      PROTOBUF_DEBUG_COUNTER("GetClassData.Virtual").Inc();
+      const ClassData* msg_class_data = msg.GetClassData();
+      // Disable caching for dynamic messages: they all have the same vtable but
+      // different `ClassData`s, which in addition can change dynamically.
+      if (!msg_class_data->is_dynamic) [[likely]] {
+        last_type_info = &typeid(msg);
+        last_class_data = msg_class_data;
+      }
+      return msg_class_data;
+    }
+#else
     return msg.GetClassData();
+#endif  // defined(PROTOBUF_RTTI) && !defined(PROTOBUF_CUSTOM_VTABLE)
   } else {
     PROTOBUF_DEBUG_COUNTER("GetClassData.Constexpr").Inc();
     return MessageTraits<T>::class_data();