我热衷于研究Scala,并提出了一个似乎无法找到答案的基本问题:一般来说,Scala和Java在性能和内存使用方面是否有所不同?
我热衷于研究Scala,并提出了一个似乎无法找到答案的基本问题:一般来说,Scala和Java在性能和内存使用方面是否有所不同?
Answers:
Scala使得无需意识到即可轻松使用大量内存。这通常非常强大,但有时可能很烦人。例如,假设您有一个字符串数组(称为array
),以及从这些字符串到文件的映射(称为mapping
)。假设您要获取映射中所有来自长度大于两个的字符串的文件。在Java中,您可能
int n = 0;
for (String s: array) {
if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
if (s.length <= 2) continue;
bigEnough[n++] = map.get(s);
}
ew!辛苦了 在Scala中,执行相同操作的最紧凑的方法是:
val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)
简单!但是,除非你非常熟悉的藏品是如何工作的,你可能不知道的是,这样做会创建一个额外的中间阵列(这种方式filter
),和一个额外的对象数组的每一个元素(有mapping.get
,它返回一个选项)。它还创建了两个函数对象(一个用于过滤器,一个用于flatMap),尽管由于函数对象很小,所以这很少成为主要问题。
因此,基本上,内存使用量在原始级别上是相同的。但是Scala的库具有许多强大的方法,可让您轻松地创建大量(通常是短暂的)对象。垃圾收集器通常可以很好地处理这种垃圾,但是如果您完全不了解正在使用的内存,那么Scala可能会比Java更快地遇到麻烦。
请注意,计算机语言基准游戏Scala代码以类似Java的风格编写,以获得类似Java的性能,因此具有类似Java的内存使用量。您可以在Scala中做到这一点:如果您编写的代码看起来像高性能的Java代码,那么它将是高性能的Scala代码。(您也许可以用更惯用的Scala风格编写它,但仍然可以获得良好的性能,但这取决于具体情况。)
我应该补充一点,每花时间进行编程,我的Scala代码通常比Java代码更快,因为在Scala中,我可以花更少的精力完成乏味的非性能关键部分,并花更多的精力优化算法和性能关键部件的代码。
我是新用户,所以我无法在上面的Rex Kerr答案中添加评论(顺便说一句,允许新用户“回答”而不是“评论”)。
我注册仅是为了回应Rex在上面流行的回答“ Java,它是如此冗长而艰苦”。当然,您可以编写更简洁的Scala代码,但给出的Java示例显然很肿。大多数Java开发人员都会编写如下代码:
List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
if(s.length() > 2 && mapping.get(s) != null) {
bigEnough.add(mapping.get(s));
}
}
当然,如果我们要假装Eclipse不能为您完成大部分实际键入操作,并且每个保存的字符确实使您成为一个更好的程序员,那么您可以编写以下代码:
List b=new ArrayList();
for(String s:array)
if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));
现在,我不仅节省了输入完整的变量名和花括号的时间(使我有5秒钟的时间来思考更深的算法思想),而且我还可以在混淆竞赛中输入代码,并有可能赚取额外的现金假期。
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
像Java一样编写Scala,您可以期望发出几乎相同的字节码-度量值几乎相同。
用不可变的对象和高阶函数更“惯用地”编写它,它会变慢一些并变大一些。这种经验法则的一个例外是,当使用类型参数使用@specialised
注释的泛型对象时,这将创建更大的字节码,从而避免装箱/拆箱,从而超过Java的性能。
还值得一提的是,在编写可以并行运行的代码时,不可避免的是要增加内存/降低速度。与典型的Java代码相比,惯用的Scala代码本质上更具声明性,并且与.par
完全并行的代码通常仅相差4个字符()。
因此,如果
您是否会说现在Scala代码要慢25%或快3倍?
正确答案取决于您如何定义“性能” :)
.par
2.9中的内容。
.par
。
map
方法的Scala程序的数量将很少。
计算机语言基准游戏:
速度测试 Java / scala 1.71 / 2.25
内存测试 Java / scala 66.55 / 80.81
因此,该基准测试表明Java速度提高了24%,scala使用的内存增加了21%。
总而言之,这没什么大不了的,在现实世界中的应用程序中也没关系,在现实世界中,大多数时间都由数据库和网络消耗。
底线:如果Scala使您和您的团队(以及您离开时接管项目的人们)更具生产力,那么您应该坚持下去。
其他人已经就紧密循环回答了这个问题,尽管我评论过的Rex Kerr的示例之间似乎存在明显的性能差异。
这个答案的确是针对那些可能研究将紧环优化作为设计缺陷的人。
我对Scala还是比较陌生的(大约一年左右),但是到目前为止,它的感觉是它使您可以相对轻松地推迟设计,实现和执行的许多方面(具有足够的背景知识和实验:)
延期设计功能:
延迟的实现功能:
延迟执行功能:(对不起,没有链接)
对我来说,这些功能是帮助我们踏上快速,紧凑应用程序之路的那些功能。
雷克斯·克尔(Rex Kerr)的示例在执行的哪些方面被推迟方面有所不同。在Java示例中,将内存分配推迟到计算出内存大小为止,而在Scala示例中,内存分配推迟了映射查找。在我看来,它们似乎是完全不同的算法。
在我的Java示例中,我认为这更多的是苹果对苹果的等效:
val bigEnough = array.collect({
case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})
没有中介的集合,没有Option
实例等,这也保留了集合类型,这样bigEnough
的类型Array[File]
- Array
的collect
实施可能会做沿着什么克尔先生的Java代码做线的东西。
我上面列出的延迟设计功能还将使Scala的collection API开发人员可以在将来的版本中实现特定于Array的快速collect实现,而不会破坏API。我指的是迈向速度之路。
也:
val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)
withFilter
我在这里使用的方法不是filter
解决中间集合问题,但仍然存在Option实例问题。
Scala中简单执行速度的一个示例是日志记录。
在Java中,我们可能会这样写:
if (logger.isDebugEnabled())
logger.debug("trace");
在Scala中,这只是:
logger.debug("trace")
因为要在Scala中调试的消息参数的类型为“ => String
”,我认为它是一种无参数函数,该函数在求值时执行,但文档称其为传递名称。
编辑{Scala中的函数是对象,因此这里有一个额外的对象。在我的工作中,琐碎对象的重量值得消除不必要评估日志消息的可能性。}
这并不能使代码更快,但确实可以使代码更快,并且我们不太可能具有遍历和清理其他人代码的经验。
对我来说,这是Scala中一个一致的主题。
硬代码无法说明为什么Scala更快,尽管确实暗示了一点。
我觉得这是代码重用和Scala中代码质量上限的结合。
在Java中,出色的代码通常被迫变成难以理解的混乱,因此在生产质量的API中实际上并不可行,因为大多数程序员都无法使用它。
我非常希望Scala可以让我们当中的爱因斯坦实现更强大的API,这些API可能通过DSL来表达。Scala中的核心API已经走了这条路。
Java和Scala都可以编译为JVM字节码,因此差别不大。您可以获得的最佳比较可能是在计算机语言基准测试游戏上,该游戏本质上说Java和Scala都具有相同的内存使用率。Scala是仅略慢于Java的一些列出的基准,但可能仅仅是因为计划的实施是不同的。
确实,他们俩是如此亲密,因此不值得担心。通过使用更具表达力的语言(例如Scala)所带来的生产率提高,所带来的价值远远超过最小(如果有)的性能损失。
Java and Scala both compile down to JVM bytecode,
与“ a”相结合,这是一个真实的陈述,它只是一个修辞手法,而不是一个有争议的结论。so
diffence isn't that big.
so
Java示例实际上不是典型应用程序的惯用语。这样的优化代码可以在系统库方法中找到。但是,它将使用正确类型的数组,即File [],并且不会引发IndexOutOfBoundsException。(用于计数和加法的不同过滤条件)。我的版本是(总是(!)带有花括号,因为我不想花一个小时来搜索错误,该错误是由于节省2秒以在Eclipse中按一个键而引入的)。
List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
if(s.length() > 2) {
File file = mapping.get(s);
if (file != null) {
bigEnough.add(file);
}
}
}
但是我可以从当前项目中带给您许多其他丑陋的Java代码示例。我试图通过排除常见的结构和行为来避免常见的复制和修改编码风格。
在我的抽象DAO基类中,我有一个用于公共缓存机制的抽象内部类。对于每种具体的模型对象类型,都有一个抽象DAO基类的子类,其中该内部类被子类化,以提供一种方法的实现,该方法在从数据库加载业务对象时创建该业务对象。(我们不能使用ORM工具,因为我们通过专有API访问另一个系统。)
这种子类化和实例化代码在Java中根本不清楚,在Scala中可读性很强。