简体   繁体   English

Scala:按名称呼叫,按值性能呼叫

[英]Scala: call by name and call by value performance

Consider the following code: 请考虑以下代码:

class A(val name: String)

Compare the two wrappers of A : 比较A的两个包装器:

class B(a: A){
  def name: String = a.name
}

and

class B1(a: A){
  val name: String = a.name
}

B and B1 have the same functionality. BB1具有相同的功能。 How is the memory efficiency and computation efficiency comparison between them? 它们之间的内存效率和计算效率如何比较? Will Scala compiler view them as the same thing? Scala编译器会将它们视为同一个东西吗?

First, I'll say that micro optimizations questions are usually tricky to answer for their lack of context. 首先,我会说微观优化问题通常很难回答他们缺乏背景。 Additionally, this question has nothing to do with call by name or call by value, as non of your examples are call by name. 此外,此问题与按名称调用或按值调用无关,因为您的示例中没有按名称调用。

Now, let's compile your code with scalac -Xprint:typer and see what gets emitted: 现在,让我们用scalac -Xprint:typer编译你的代码,看看会发出什么:

class B extends scala.AnyRef {                              
  <paramaccessor> private[this] val a: A = _;               
  def <init>(a: A): B = {                                   
    B.super.<init>();                                       
    ()                                                      
  };                                                        
  def name: String = B.this.a.name                          
};                                                          

class B1 extends scala.AnyRef {                             
  <paramaccessor> private[this] val a: A = _;               
  def <init>(a: A): B1 = {                                  
    B1.super.<init>();                                      
    ()                                                      
  };                                                        
  private[this] val name: String = B1.this.a.name;          
  <stable> <accessor> def name: String = B1.this.name       
};

In class B , we hold a reference to a , and have a method name which calls the name value on A . 在课堂上B ,我们坚持一个参考a ,并有一个方法name它调用name价值A

In class B1 , we store name locally, since it is a value of B1 directly, not a method. B1类中,我们在本地存储name ,因为它直接是B1的值,而不是方法。 By definition, val declarations in have a method generated for them and that is how they're accessed. 根据定义, val声明中有一个为它们生成的方法,这就是它们的访问方式。

This boils down to the fact that B1 holds an additional reference to the name string allocated by A . 这可以归结为B1保存了对A分配的name字符串的附加引用。 Is this significant in any way from a performance perspective? 从性能角度来看,这有何重要意义? I don't know. 我不知道。 It looks negligible to me under general question you've posted, but I wouldn't be to bothered with this unless you've profiled your application and found this a bottleneck. 在你发布的一般性问题下,它对我来说可以忽略不计,但我不会为此烦恼, 除非你已经分析了你的应用程序并发现这是一个瓶颈。


Lets take this one step further, and run a simple JMH micro benchmark on this: 让我们更进一步,并在此基础上运行一个简单的JMH微基准:

[info] Benchmark                        Mode  Cnt    Score     Error   Units
[info] MicroBenchClasses.testB1Access  thrpt   50    296.291 ± 20.787    ops/us
[info] MicroBenchClasses.testBAccess   thrpt   50    303.866 ± 5.435    ops/us
[info] MicroBenchClasses.testB1Access   avgt    9    0.004 ±   0.001   us/op
[info] MicroBenchClasses.testBAccess    avgt    9    0.003 ±   0.001   us/op

We see that call times are identical, since in both times we're invoking a method. 我们看到调用时间是相同的,因为在这两个时间我们都在调用一个方法。 One thing we can notice is that the throughput on B is higher, why is that? 我们可以注意到的一件事是B上的吞吐量更高,为什么呢? Lets look at the byte code: 让我们看一下字节码:

B: B:

public java.lang.String name();
  Code:
       0: aload_0
       1: getfield      #20                 // Field a:Lcom/testing/SimpleTryExample$A$1;
       4: invokevirtual #22                 // Method com/testing/SimpleTryExample$A$1.name:()Ljava/lang/String;
       7: areturn

B1: B1:

public java.lang.String name();
    Code:
       0: aload_0
       1: getfield      #19                 // Field name:Ljava/lang/String;
       4: areturn

It isn't trivial to understand why a getfield would be slower than a invokevirtual , but in the end the JIT may inline the getter call to name . 理解为什么getfieldinvokevirtual慢,但最终JIT可能会将getter调用内联到name这并非易事。 This goes to show you that you should take nothing for granted, benchmark everything. 这表明你不应该把任何事情视为理所当然,对所有事情进行基准测试。

Code for test: 测试代码:

import java.util.concurrent.TimeUnit
import org.openjdk.jmh.annotations._

/**
  * Created by Yuval.Itzchakov on 19/10/2017.
  */
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 3)
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
class MicroBenchClasses {
  class A(val name: String)
  class B(a: A){
    def name: String = a.name
  }

  class B1(a: A){
    val name: String = a.name
  }

  var b: B = _
  var b1: B1 = _
  @Setup
  def setup() = {
    val firstA = new A("yuval")
    val secondA = new A("yuval")

    b = new B(firstA)
    b1 = new B1(secondA)
  }

  @Benchmark
  def testBAccess(): String = {
    b.name
  }

  @Benchmark
  def testB1Access(): String = {
    b1.name
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM