更高效的反射调用方式被我找到了!

背景

在使用Java进行开发时,我们会不可避免的使用到大量的反射操作,比如Spring Boot会在接收到HTTP请求时,利用反射Controller调用接口中的对应方法,或是Jackson框架使用反射来解析json中的数据给对应字段进行赋值,我们可以编写一个简单的JMH测试来评估一下通过反射调用来创建对象的性能,与直接调用对象构造方法之间的差距:

@BenchmarkMode(value = Mode.AverageTime)  
@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MILLISECONDS)  
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)  
@State(Scope.Thread)  
@OutputTimeUnit(TimeUnit.NANOSECONDS)  
public abstract class JmhTest {  public static void runTest(Class<?> launchClass) throws RunnerException {  Options options = new OptionsBuilder().include(launchClass.getSimpleName()).build();  new Runner(options).run();  }  
}package cn.zorcc.common.jmh;  import org.openjdk.jmh.annotations.Benchmark;  
import org.openjdk.jmh.annotations.Param;  
import org.openjdk.jmh.infra.Blackhole;  
import org.openjdk.jmh.runner.RunnerException;  import java.lang.invoke.MethodHandle;  
import java.lang.invoke.MethodHandles;  
import java.lang.invoke.MethodType;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Method;  public class ReflectionTest extends JmhTest {  @Param({"10", "100", "1000", "10000"})  private int size;  static class Test {  private int integer;  public int getInteger() {  return integer;  }  public void setInteger(int integer) {  this.integer = integer;  }  }  @Benchmark  public void testDirectCall(Blackhole bh) {  for(int i = 0; i < size; i++) {  Test test = new Test();  bh.consume(test);  test.setInteger(i);  bh.consume(test.getInteger());  }  }  @Benchmark  public void testNormalReflection(Blackhole bh) {  try{  Constructor<Test> constructor = Test.class.getDeclaredConstructor();  Method setter = Test.class.getDeclaredMethod("setInteger", int.class);  Method getter = Test.class.getDeclaredMethod("getInteger");  for(int i = 0; i < size; i++) {  Test test = constructor.newInstance();  bh.consume(test);  setter.invoke(test, i);  int integer = (int) getter.invoke(test);  bh.consume(integer);  }  }catch (Throwable e) {  throw new UnknownError();  }  }  public static void main(String[] args) throws RunnerException {  runTest(ReflectionTest.class);  }  
}

测试代码使用了Java Microbenchmark Harness (JMH)的注解,用于标记和配置一个微基准测试类。JMH是一个用于编写、运行和分析Java微基准测试的框架。它可以帮助你编写出更可靠、可重复的基准测试。有兴趣的小伙伴可以去了解一下

Test类中,具有一个简单的int类型的变量,我们分别测试直接调用构造方法,赋值然后取值,以及使用ConstructorMethod进行普通反射调用之间的性能对比,注意一定要将构造出来的对象使用Blackhole.consume()方法给吃掉,这样JVM才不会把没有使用到的变量给直接的优化掉,得出错误的测试结果,以上代码在笔者的机器上运行的结果如下:

Benchmark                                  (size)  Mode  Cnt       Score       Error  Units
ReflectionTest.testDirectCall                  10  avgt   50      10.584 ±     0.141  ns/op
ReflectionTest.testDirectCall                 100  avgt   50     108.301 ±     1.129  ns/op
ReflectionTest.testDirectCall                1000  avgt   50    1068.026 ±    12.312  ns/op
ReflectionTest.testDirectCall               10000  avgt   50   10660.596 ±   148.673  ns/op
ReflectionTest.testNormalReflection            10  avgt   50     145.483 ±     1.300  ns/op
ReflectionTest.testNormalReflection           100  avgt   50    1131.994 ±    19.586  ns/op
ReflectionTest.testNormalReflection          1000  avgt   50   13461.067 ±   130.624  ns/op
ReflectionTest.testNormalReflection         10000  avgt   50  148811.318 ±  5766.679  ns/op

可能有些小伙伴对JMH并不熟悉,看不懂运行结果,我大概阐述下运行结果表头含义。

  • Benchmark这是基准测试方法的名称。上面的例子就是testDirectCalltestNormalReflection
  • (size)是参数化测试中使用的参数名称。在上面的例子中,size参数有四个不同的值(10, 100, 1000, 10000),用于控制基准测试中循环的次数或对象的大小等。JMH会为每一个参数值运行基准测试,并给出相应的结果。
  • Mode:这表示测量的模式。在我们上面的例子中,它被设置为AverageTime,表示测量的是每次迭代的平均执行时间。JMH支持多种测量模式,如吞吐量(每秒操作数)、平均时间、单次操作时间等。
  • Cnt:这表示迭代次数。它指的是JMH在预热阶段和测量阶段中运行的测试方法迭代次数。注意,这里的迭代次数可能与我们在注解中设置的迭代次数不同,因为JMH可能会为了获得更稳定的结果而自动调整迭代次数。
  • Score:这是主要的性能指标。在你的例子中,它表示每次迭代的平均执行时间。这是基准测试方法性能的主要度量标准。
  • Error:这表示Score的误差范围。它表示Score值的置信区间,通常用于评估测量结果的稳定性和可靠性。误差范围越小,说明测量结果越稳定。
  • Units:这表示Score和Error的单位。在上面的例子中,单位是Units,这实际上是一个占位符,因为我们在@OutputTimeUnit(TimeUnit.NANOSECONDS)注解中指定了输出单位为纳秒(NANOSECONDS)。所以,正确的单位应该是ns(纳秒)。

可以看到,使用反射的性能比起直接调用来讲有非常大的差距,尤其是在这种极其简单的对象创建场景中,但是使用反射是很多情况下我们不得不采用的一个做法,那么我们有没有什么办法来尽可能优化一下反射调用的性能呢?

先让我们试一下MethodHandle提供的方法调用模型,MethodHandle是自JDK7版本后开始推出的,用于替换旧反射调用的新方式,相比起原有的反射调用,提供了更多的交互方式,并且具备对Java方法调用和Native方法调用一致的模型,我们可以简单的创建一个用例进行测试:

@Benchmark  
public void testMethodHandleReflection(Blackhole bh) {  try{  MethodHandles.Lookup lookup = MethodHandles.lookup();  MethodType constructorType = MethodType.methodType(void.class);  MethodHandle constructorHandle = lookup.findConstructor(Test.class, constructorType);  MethodHandle iSetter = lookup.findSetter(Test.class, "integer", int.class);  MethodHandle iGetter = lookup.findGetter(Test.class, "integer", int.class);  for(int i = 0; i < size; i++) {  Test test = (Test) constructorHandle.invokeExact();  bh.consume(test);  iSetter.invokeExact(test, i);  int integer = (int) iGetter.invokeExact(test);  bh.consume(integer);  }  }catch (Throwable e) {  throw new UnknownError();  }  
}

实测的结果则更加的不尽人意:

ReflectionTest.testMethodHandleReflection      10  avgt   50    1346.515 ±    17.347  ns/op
ReflectionTest.testMethodHandleReflection     100  avgt   50    2355.083 ±    37.358  ns/op
ReflectionTest.testMethodHandleReflection    1000  avgt   50  456694.572 ± 31415.118  ns/op
ReflectionTest.testMethodHandleReflection   10000  avgt   50  982008.110 ± 46807.572  ns/op

可以看到,使用MethodHandle与使用普通反射之间的性能差距,就和普通反射与直接调用之间的差距一样大,事实上在JDK18以后,根据# JEP 416: Reimplement Core Reflection with Method Handles 使用java.lang.reflectjava.lang.invoke的相关API已经进行了相应的底层重构,转而使用MethodHandle进行实现,很明显,在使用java.lang.reflectjava.lang.invoke中的方法时,与直接使用MethodHandle相比,具备了更多的优化工作,根据官方的说法,在使用MethodHandle时因将字段尽可能定义为static final,这样JVM可以将其进行常量折叠,从而实现巨大的性能提升,让我们修改一下以上的测试代码:

private static final MethodHandle constructorHandle;  
private static final MethodHandle iSetter;  
private static final MethodHandle iGetter;  
static {  try{  MethodHandles.Lookup lookup = MethodHandles.lookup();  MethodType constructorType = MethodType.methodType(void.class);  constructorHandle = lookup.findConstructor(Test.class, constructorType);  iSetter = lookup.findSetter(Test.class, "integer", int.class);  iGetter = lookup.findGetter(Test.class, "integer", int.class);  }catch (Throwable e) {  throw new UnknownError();  }  
}@Benchmark  
public void testMethodHandleReflection(Blackhole bh) {  try{  for(int i = 0; i < size; i++) {  Test test = (Test) constructorHandle.invokeExact();  bh.consume(test);  iSetter.invokeExact(test, i);  int integer = (int) iGetter.invokeExact(test);  bh.consume(integer);  }  }catch (Throwable e) {  throw new UnknownError();  }  
}

得到了如下的数据:


ReflectionTest.testMethodHandleReflection      10  avgt   50       9.825 ±    0.084  ns/op
ReflectionTest.testMethodHandleReflection     100  avgt   50      99.174 ±    1.128  ns/op
ReflectionTest.testMethodHandleReflection    1000  avgt   50     997.094 ±   11.961  ns/op
ReflectionTest.testMethodHandleReflection   10000  avgt   50   10212.014 ±  215.662  ns/op

突然之间,我们的反射调用和直接调用的性能已经完全一致了,那么这是不是意味着,我们想要的功能已经完全实现了呢?事实上并未如此,如果我们必须在static final中指定需要使用到的反射字段,那么就相当于损失了绝大多数的灵活性,在实际操作中可行性并不高。

同样的,我们可以试一试,将直接使用java.lang.reflectjava.lang.invoke的函数所需的对象先构建并缓存在本地,再测试一下其对应的性能:

private Constructor<Test> c;  
private Method setter;  
private Method getter;@Setup   
public void setup() {try{  this.c = Test.class.getDeclaredConstructor();  this.setter = Test.class.getDeclaredMethod("setInteger", int.class);  this.getter = Test.class.getDeclaredMethod("getInteger");}catch (Throwable e) {  throw new UnknownError();  }
}@Benchmark  
public void testNormalReflection(Blackhole bh) {  try{  for(int i = 0; i < size; i++) {  Test test = c.newInstance();  bh.consume(test);  setter.invoke(test, i);  int integer = (int) getter.invoke(test);  bh.consume(integer);  }  }catch (Throwable e) {  throw new UnknownError();  }  
}

与在测试MethodHandle时我们将需要初始化的变量定义为static final不同,此处我们直接将其定义为private变量,在JMH框架中提供的@Setup函数中进行初始化,更贴合的模拟我们在运行时进行创建的行为,测试得到的结果如下:

ReflectionTest.testNormalReflection      10  avgt   50     152.242 ±    5.625  ns/op
ReflectionTest.testNormalReflection     100  avgt   50    1495.302 ±   21.467  ns/op
ReflectionTest.testNormalReflection    1000  avgt   50   16917.774 ±  420.810  ns/op
ReflectionTest.testNormalReflection   10000  avgt   50  143252.377 ± 2150.908  ns/op

可以看到,使用普通反射的方式,无论是每次都获取新的ConstructorMethod对象进行创建,还是通过提前缓存的形式进行加载,性能表现是相似的,这也使得通用的反射调用方式在各类通用场景下都能够具备比较不错的表现。

鉴于我们之前的这些测试结果,如果想要进一步的提升反射的性能,只能考虑使用类生成的方式,在编译期创建出MethodHandle的静态变量,让JVM帮我们去自动内联,当然,类生成的方式一定可以拥有非常不错的性能,但是使用ByteBuddyAsm框架进行类生成的代码相对而言过于繁琐,目前# JEP 457: Class-File API (Preview) 特性正处于preview阶段,可以帮助我们更加简化的在JVM中进行类生成,但是目前我们还无法对其进行使用。

解决方案

Lambda表达式贯穿了我们日常的开发中的所有角落,且Lambda表达式本身的性能不会差,否则JDK内部绝对不会如此大量的使用它,Lambda表达式的生成方式也并不复杂,其背后的核心方法是通过LambdaMetafactory.metafactory()方法生成对应的方法调用,我们可是实现以下的代码来完成对应构造函数,getter方法和setter方法向Lambda函数的转换:

private Supplier<Test> constructor;  
private BiConsumer<Object, Object> setConsumer;  
private Function<Test, Integer> getFunction;@Setup  
public void setup() throws Throwable {MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ReflectionTest.class, MethodHandles.lookup());  this.constructor = lambdaGenerateConstructor(lookup);  this.setConsumer = lambdaGenerateSetter(lookup);  this.getFunction = lambdaGenerateGetter(lookup);
}@SuppressWarnings("unchecked")  
private Supplier<Test> lambdaGenerateConstructor(MethodHandles.Lookup lookup) throws Throwable {  MethodHandle cmh = lookup.findConstructor(Test.class, MethodType.methodType(void.class));  CallSite c1 = LambdaMetafactory.metafactory(lookup,  "get",  MethodType.methodType(Supplier.class),  MethodType.methodType(Object.class), cmh, MethodType.methodType(Test.class));  return (Supplier<Test>) c1.getTarget().invokeExact();  
}  @SuppressWarnings("unchecked")  
private BiConsumer<Object, Object> lambdaGenerateSetter(MethodHandles.Lookup lookup) throws Throwable {  MethodHandle setHandle = lookup.findVirtual(Test.class, "setInteger", MethodType.methodType(void.class, int.class));  CallSite callSite = LambdaMetafactory.metafactory(lookup,  "accept",  MethodType.methodType(BiConsumer.class),  MethodType.methodType(void.class, Object.class, Object.class),  setHandle,  MethodType.methodType(void.class, Test.class, Integer.class));  return (BiConsumer<Object, Object>) callSite.getTarget().invokeExact();  
}  @SuppressWarnings("unchecked")  
private Function<Test, Integer> lambdaGenerateGetter(MethodHandles.Lookup lookup) throws Throwable {  MethodHandle getHandle = lookup.findVirtual(Test.class, "getInteger", MethodType.methodType(int.class));  CallSite getSite = LambdaMetafactory.metafactory(  lookup,  "apply",  MethodType.methodType(Function.class),  MethodType.methodType(Object.class, Object.class),  getHandle,  MethodType.methodType(Integer.class, Test.class)  );  return (Function<Test, Integer>) getSite.getTarget().invokeExact();  
}@Benchmark  
public void testLambda(Blackhole bh) {  for(int i = 0; i < size; i++) {  Test test = constructor.get();  bh.consume(test);  setConsumer.accept(test, i);  int integer = getFunction.apply(test);  bh.consume(integer);  }  
}  @Benchmark  
public void testLambdaGeneration(Blackhole bh) throws Throwable {  MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ReflectionTest.class, MethodHandles.lookup());  bh.consume(lambdaGenerateConstructor(lookup));  bh.consume(lambdaGenerateSetter(lookup));  bh.consume(lambdaGenerateGetter(lookup));  
}

测试分为两个步骤,一个是测试Lambda表达式的生成性能,一个是测试Lambda表达式的运行性能,这两个指标对我们来说都非常的重要,得到的结果如下:

ReflectionTest.testLambdaGeneration   10000  avgt   50   92486.909 ±  62638.147  ns/op
Benchmark                  (size)  Mode  Cnt      Score     Error  Units
ReflectionTest.testLambda      10  avgt   50     10.720 ±   0.087  ns/op
ReflectionTest.testLambda     100  avgt   50    105.001 ±   1.312  ns/op
ReflectionTest.testLambda    1000  avgt   50   1020.406 ±   9.990  ns/op
ReflectionTest.testLambda   10000  avgt   50  10198.842 ± 143.259  ns/op

可以看到,通过模拟Lambda表达式生成的方式,调用构造函数以及getset方法的性能,与直接调用是几乎完全一致的,这也就达成了我们想要的效果,但是Lambda生成的性能非常不容乐观,与直接使用箭头函数进行生成的性能有着天壤之别,好在如果Lambda表达式没有捕获任何的外部变量,比如我们在示例中调用的getset方法,那么生成的方法是可以被缓存起来重复使用的,如果使用的基数本身比较大,在多次调用的开销权衡中,初始化的开销就可以被忽略不计。

小结

本文介绍了一种在Java中的新的反射调用方式,即使用类似于Lambda表达式的生成的方式进行反射,可以将一些简单的方法,例如getset方法,直接转化为相应的Lambda表达式来调用,虽然可以做到和直接调用一致的性能,但是该方法的生成开销比较大,需要在频繁调用的场景中进行缓存,才能起到比较好的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/760019.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#进阶-用于Excel处理的程序集

在.NET开发中&#xff0c;处理Excel文件是一项常见的任务&#xff0c;而有一些优秀的Excel处理包可以帮助开发人员轻松地进行Excel文件的读写、操作和生成。本文介绍了NPOI、EPPlus和Spire.XLS这三个常用的.NET Excel处理包&#xff0c;分别详细介绍了它们的特点、示例代码以及…

【Frida】10_用鼠标自动标记棋盘上的雷区(一键过关)

&#x1f6eb; 系列文章导航 【Frida】 00_简单介绍和使用 https://blog.csdn.net/kinghzking/article/details/123225580【Frida】 01_食用指南 https://blog.csdn.net/kinghzking/article/details/126849567【Frida】 03_初识frida-node https://blog.csdn.net/kinghzking/ar…

AI程序员诞生:对程序员的影响与未来展望

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 方向一&#xff1a;AI程序员的优势分析 方向二&#xff1a;AI程序员局限性 方向三&#xff1a;对程序员职业的影响 方向四&…

【吊打面试官系列】Redis篇 - Redis 的回收策略(淘汰策略)?

大家好&#xff0c;我是锋哥。今天分享关于 Redis 的回收策略&#xff08;淘汰策略&#xff09;面试题&#xff0c;希望对大家有帮助&#xff1b; volatile-lru &#xff1a;从已设置过期时间的数据集&#xff08;server.db[i].expires&#xff09;中挑选最近最少使用的数据淘汰…

高通8255芯片首次烧写方法

高通8255芯片首次烧写需要进行分区烧写&#xff0c;方法如下&#xff1a; 目录 一&#xff1a;QFIL安装 二&#xff1a;关于QFIL详细文档 三&#xff1a;简要分区烧写方法 1烧写 meta build 2 然后重启一下机器 3 烧写 flat build 四&#xff1a;正常烧写程序 一&#…

成为高效Java工程师的干货笔记

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流&#xff0c;摸鱼划水的小伙伴&#xff0c;请点击【全栈技术交流群】 作为一名Java工程师&…

Lambda函数与Selenium WebDriverWait类一起使用

Lambda函数是一种匿名函数&#xff0c;也称为内联函数或者lambda表达式。它们在Python中用于创建简短的、一次性的函数。Lambda函数通常用于在代码中传递函数作为参数&#xff0c;或者在需要一个简单的函数&#xff0c;但不想正式定义一个函数的情况下使用。 Lambda函数的特点…

好用电脑桌面便签是什么?电脑好用便签软件推荐

面对电脑屏幕&#xff0c;我常常感到一种无形的压力。繁杂的工作、琐碎的事务&#xff0c;仿佛都在这个小小的屏幕里与我争夺注意力。每当这时&#xff0c;我就特别需要一个能随时记录我重要事项的工具&#xff0c;让我能在忙碌中保持清醒的头脑。 有一天&#xff0c;我发现了…

产品|快!精!强!点点田企业版功能大升级

在诸如农业补贴、农情监测以及种植监管等场景中&#xff0c;农业遥感数据获取门槛高、行业客户软件开发经验不足等痛点一直存在。 针对这一挑战&#xff0c;珈和科技开发了点点田企业版产品&#xff0c;提供农业遥感数据服务&#xff0c;以API交付的方式降低数据获取门槛&…

新手leetcode 126周赛被拷打篇

100262. 求出加密整数的和 难度&#xff1a;568 给你一个整数数组 nums &#xff0c;数组中的元素都是 正 整数。定义一个加密函数 encrypt &#xff0c;encrypt(x) 将一个整数 x 中 每一个 数位都用 x 中的 最大 数位替换。比方说 encrypt(523) 555 且 encrypt(213) 333 。…

外包干了1个月,技术明显进步。。。

我是一名大专生&#xff0c;自19年通过校招进入湖南某软件公司以来&#xff0c;便扎根于功能测试岗位&#xff0c;一晃便是近四年的光阴。今年8月&#xff0c;我如梦初醒&#xff0c;意识到长时间待在舒适的环境中&#xff0c;已让我变得不思进取&#xff0c;技术停滞不前。更令…

Uibot (RPA设计软件)财务会计Web应用自动化(批量开票机器人)

Uibot (RPA设计软件&#xff09;Mage AI智能识别&#xff08;发票识别&#xff09;———机器人的小项目友友们可以参考小北的课前材料五博客~ (本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们…

YOLOv5目标检测学习(7):验证部分val.py简要分析;训练、验证、推理三文件的关系

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、val.py的大致结构如下&#xff1a;1.0 准备工作1.获取文件路径2.存储预测信息为.txt文件3.存储预测信息为coco格式的.json文件 1.1 主函数main&#xff1a;…

[MTK6771] android13系统启用OMAPI 支持esim.me

OMAPI是啥&#xff1f;看看谷歌的解释&#xff1a; 说了一大堆懂的人不需要看&#xff0c;不懂的还是看不懂&#xff0c;我就是后者 总之说人话就是&#xff0c;像SIM卡&#xff0c;NFC这类模块需要用到这个东西&#xff0c;那么接着往下看 上层APP想要使用这个OMAPI供应商稳…

五、分支结构

一、程序的组织结构 无论程序是大是小&#xff0c;都可以用顺序结构、选择结构和循环结构表示 二、单分支结构 单分支结构&#xff1a;如果表达式的值是True就执行代码&#xff0c;如果表达式的值是False就跳过语句执行后面语句 ageint(input(请输入你的年龄&#xff1a;)) i…

富格林:正规观念阻挠诱导被骗

富格林悉知&#xff0c;现货黄金一直是市场上备受关注的投资产品。新手投资者由于经验不足以及没有正规观念指引&#xff0c;容易出现诱导被骗的情况。事实上&#xff0c;拥有正规的观念指引可以在很大程度阻挠我们诱导被骗。下面富格林给大家分享一些阻挠被骗的做单技巧。 找到…

Web框架开发-Django-数据库表的单表查询

一、添加表记录 对于单表有两种方式 方式一: 1 2 book_obj=models.Book(title="python全栈开发",price=100,publishData="2015-08-08", author=张三, publish=机械工业出版社) book_obj.save() 方式二: 1 2 # 方式二: models.Book.objects.cr…

综合系列之大四学生找工作的自荐信模板推荐

模板一 尊敬的招聘负责人&#xff1a; 您好&#xff01;我是一名即将毕业的大四学生&#xff0c;非常荣幸有机会向您自荐&#xff0c;希望能有机会加入贵公司。 在学校期间&#xff0c;我始终保持积极进取的态度&#xff0c;不断努力学习专业知识&#xff0c;提升自己的综合…

C语言例3-31:位移位运算的例子

1. 位移位运算符 左移 <<右移 >>运算对象只能是整型或字符型数据参与位移位运算时&#xff0c;运算对象以二进制形式进行相应的按位运算。 2. 运算规则 移位时&#xff0c;移出的位数全部丢弃&#xff0c;移出的空位补入的数与左移还是右移有关。若是左移&#…

“贷”动“新质生产力”?各大银行出手了!(附产业图谱下载)

官.网地址&#xff1a;合合TextIn - 合合信息旗下OCR云服务产品 自去年9月首次提出以来&#xff0c;新质生产力的重要性不断得到强化&#xff0c;今年两会期间&#xff0c;更是被写入了政府工作报告并被列为了十大任务之首。 伴随新质生产力培育元年拉开序幕&#xff0c;金融…