簡體   English   中英

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

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

請考慮以下代碼:

class A(val name: String)

比較A的兩個包裝器:

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

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

BB1具有相同的功能。 它們之間的內存效率和計算效率如何比較? Scala編譯器會將它們視為同一個東西嗎?

首先,我會說微觀優化問題通常很難回答他們缺乏背景。 此外,此問題與按名稱調用或按值調用無關,因為您的示例中沒有按名稱調用。

現在,讓我們用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       
};

在課堂上B ,我們堅持一個參考a ,並有一個方法name它調用name價值A

B1類中,我們在本地存儲name ,因為它直接是B1的值,而不是方法。 根據定義, val聲明中有一個為它們生成的方法,這就是它們的訪問方式。

這可以歸結為B1保存了對A分配的name字符串的附加引用。 從性能角度來看,這有何重要意義? 我不知道。 在你發布的一般性問題下,它對我來說可以忽略不計,但我不會為此煩惱, 除非你已經分析了你的應用程序並發現這是一個瓶頸。


讓我們更進一步,並在此基礎上運行一個簡單的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

我們看到調用時間是相同的,因為在這兩個時間我們都在調用一個方法。 我們可以注意到的一件事是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:

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

理解為什么getfieldinvokevirtual慢,但最終JIT可能會將getter調用內聯到name這並非易事。 這表明你不應該把任何事情視為理所當然,對所有事情進行基准測試。

測試代碼:

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