2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!有关如何工作的更多详细信息,请参阅 关于 页面。

0 投票
编译器

我意识到依赖反射调用这类方法在性能上并不是一个好的选择,但它不应该导致不正确或危险的行为。

这里它似乎会引发对输入 long 类型的静默向下转型,从而得到截断的整数输出

user> (defn f (link: a b) (Math/abs (- a b)))
反射警告,NO_SOURCE_PATH:1:15 - 不能解析出 abs 的调用。

'user/f

user> (f 1000000000000 2000000000000)
727379968
user> (class (f 1000000000000 2000000000000))
java.lang.Integer
user> (defn f (link: ^long a ^long b) (Math/abs (- a b)))

'user/f

user> (f 1000000000000 2000000000000)
1000000000000
user> (class (f 1000000000000 2000000000000))
java.lang.Long

9 个答案

0 投票

评论由:matthjw 提出

为了更简单地复现此问题

user> (#(Math/abs %) 1000000000000)
反射警告,NO_SOURCE_PATH:1:3 - 不能解析出 abs 的调用。
727379968

0 投票

评论由:jafingerhut 提出

我在 Ubuntu 12.04.2 上的 Java 6 JVM 上能够复现您看到的行为。

java 版本 "1.6.0_27"
OpenJDK 运行时环境 (IcedTea6 1.12.5) (6b27-1.12.5-0ubuntu0.12.04.1)
OpenJDK 64 位服务器 VM (构建 20.0-b12,混合模式)

java 版本 "1.6.0_45"
Java(TM) SE 运行时环境 (构建 1.6.0_45-b06)
Java HotSpot(TM) 64 位服务器 VM (构建 20.45-b01,混合模式)

然而,我尝试了两个Java 7 JVM,它表现出以下行为,看起来更接近你希望的结果。我不知道Java 6和Java 7之间精确的区别是什么导致了这种行为差异,但这是一些证明这与Java 6与Java 7有关的证据。

用户=> (设置! 警告反射 true)
true
用户=> (定义 fn f (连接: a b) (Math/abs (- a b)))
反射警告,NO_SOURCE_PATH:1:15 - 不能解析出 abs 的调用。

'user/f

用户=> (f 1000000000000 2000000000000)
1000000000000
用户=> (类 (f 1000000000000 2000000000000))
java.lang.Long

上述行为在以下这些JVM上观察到

Ubuntu 12.04.2 加上这个JVM
java 版本 "1.7.0_21"
Java™ SE 运行时环境 (构建 1.7.0_21-b11)
Java HotSpot™ 64位服务器VM (构建 23.21-b01,混合模式)

Mac OS X 10.8.3 加上这个JVM
java 版本 "1.7.0_15"
Java™ SE 运行时环境 (构建 1.7.0_15-b03)
Java HotSpot™ 64位服务器VM (构建 23.7-b01,混合模式)

0 投票
评论由: matthjw_ 制作

啊,很有趣。
可能是Java 7中反射API工作的方式不同?

以下为字节码生成,供有兴趣的人参考

public java.lang.Object invoke(java.lang.Object);
  代码
   0: ldc #14; //String java.lang.Math
   2: invokestatic #20; //Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
   5: ldc #22; //String abs
   7: iconst_1
   8: anewarray #24; //class java/lang/Object
   11: dup
   12: iconst_0
   13: aload_1
   14: aconst_null
   15: astore_1
   16: aastore
   17: invokestatic #30; //Method clojure/lang/Reflector.invokeStaticMethod:(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;
   20: areturn
0 投票

评论由:matthjw 提出

这是一个想法(也许这是Java 7下发生的?),由于它是静态方法,并且所有可用的重载变异版本可能在编译时都是已知的,也许它可以生成以下之类的代码

(条件
(实例? 长 x)(Math/abs (long x))
(实例? 整数 x)(Math/abs (int x))
;; ...
)

0 投票

评论由:jafingerhut 提出

在Reflector.java文件中的invokeStaticMethod(Class c, String methodName, Object(args:))方法中,先调用getMethods()方法,然后调用invokeMatchingMethod()方法。getMethods()方法在Java 6和7中返回不同的顺序,分别为4个java.lang.Math/abs方法,导致在不同JVM上invokeMatchingMethod()方法选取不同的方法

java版本 "1.6.0_39"
Java(TM) SE运行环境(版本1.6.0_39-b04)
Java HotSpot(TM) 64位服务器虚拟机(版本20.14-b01,混合模式)

user=> (pprint (seq (clojure.lang.Reflector/getMethods java.lang.Math 1 "abs" true)))
(#
#
#
#)
nil

java 版本 "1.7.0_21"
Java™ SE 运行时环境 (构建 1.7.0_21-b11)
Java HotSpot™ 64位服务器VM (构建 23.21-b01,混合模式)

user=> (pprint (seq (clojure.lang.Reflector/getMethods java.lang.Math 1 "abs" true)))
(#
#
#
#)
nil

这可能意味着invokeMatchingMethod()中的不可取行为过于依赖于传递给它的方法顺序。

正如你提到的,类型提示有助于避免反射的性能显著下降。

0 投票

评论者:alexmiller

在1.8.0-alpha3版本上无法复现。

0 投票

评论者:bronsa

Alex,我使用1.8.0-master-SNAPSHOT和jdk 1.8版本能够复现。

[~]> java -version java version "1.8.0_45" Java(TM) SE运行环境(版本1.8.0_45-b14) Java HotSpot(TM) 64位服务器虚拟机(版本25.45-b02,混合模式) [~]> clj Clojure 1.8.0-master-SNAPSHOT user=> (set! *warn-on-reflection* true) true user=> (#(Math/abs %) 1000000000000) 反射警告,NO_SOURCE_PATH:2:3 - 对java.lang.Math的静态方法abs的调用无法解析(参数727379968) user=> (class *1) java.lang.Integer

Andy的的最后一条评论提到,clojure.lang.Reflector.invokeStaticMethod依赖于传递给它的方法的顺序,并且这个顺序可能在不同的jdk版本之间改变,所以可能这就是你无法复现它的原因。

0 投票

评论由:jafingerhut 提出

可能与CLJ-1921相似,或者至少有一定的共同点。

0 投票
参考:https://clojure.atlassian.net/browse/CLJ-1212 (由alexiport报告)
...