草庐IT

java - JVM 的 LookupSwitch 和 TableSwitch 的区别?

coder 2023-05-13 原文

我在理解 Java 字节码中的 LookUpSwitch 和 TableSwitch 时有些困难。

如果我理解得很好,LookUpSwitch 和 TableSwitch 都对应于 switch Java源代码的声明?为什么一个 JAVA 语句会生成 2 个不同的字节码?

每个 Jasmin 文档:

  • LookupSwitch
  • tableswitch
  • both
  • 最佳答案

    不同之处在于

  • 查找开关使用 带 key 和标签的 table
  • tableswitch 使用 一个只有标签的表格 .

  • 执行 时桌面开关 ,栈顶的int值直接作为表中的索引来抓取跳转目标并立即执行跳转。整个查找+跳转过程是一个 O(1) 操作 ,这意味着它的速度非常快。
    执行 时查找开关 ,堆栈顶部的 int 值与表中的键进行比较,直到找到匹配项,然后使用此键旁边的跳转目标执行跳转。由于查找开关表总是 必须排序 这样keyX < keyy="" for="" 每一个x=""><> O(log n) 操作 因为将使用二进制搜索算法搜索键(没有必要将 int 值与所有可能的键进行比较以找到匹配项或确定没有任何键匹配)。 O(log n) 比 O(1) 慢一些,但它仍然可以,因为许多众所周知的算法都是 O(log n) 并且这些通常被认为是快速的;甚至 O(n) 或 O(n * log n) 仍然被认为是一个很好的算法(慢/坏算法有 O(n^2)、O(n^3),甚至更糟)。
    编译器根据 switch 语句的紧凑程度来决定使用哪条指令,例如
    switch (inputValue) {
      case 1:  // ...
      case 2:  // ...
      case 3:  // ...
      default: // ...
    }
    
    上面的开关非常紧凑,没有数字“孔”。编译器会像这样创建一个 tableswitch:
     tableswitch 1 3
        OneLabel
        TwoLabel
        ThreeLabel
      default: DefaultLabel
    
    Jasmin 页面中的伪代码很好地解释了这一点:
    int val = pop();                // pop an int from the stack
    if (val < low || val > high) {  // if its less than <low> or greater than <high>,
        pc += default;              // branch to default 
    } else {                        // otherwise
        pc += table[val - low];     // branch to entry in table
    }
    
    这段代码非常清楚地说明了这种 tableswitch 是如何工作的。 valinputValue , low将是 1(开关中的最低值)和 high将是 3(开关中的最高值)。
    即使有一些孔,开关也可以很紧凑,例如
    switch (inputValue) {
      case 1:  // ...
      case 3:  // ...
      case 4:  // ...
      case 5:  // ...
      default: // ...
    }
    
    上面的开关“几乎紧凑”,它只有一个孔。编译器可以生成以下指令:
     tableswitch 1 6
        OneLabel
        FakeTwoLabel
        ThreeLabel
        FourLabel
        FiveLabel
      default: DefaultLabel
    
      ; <...code left out...>
    
      FakeTwoLabel:
      DefaultLabel:
        ; default code
    
    如您所见,编译器必须为 2 添加一个假 case,FakeTwoLabel .由于 2 不是开关的实际值,FakeTwoLabel实际上是一个标签,可以准确地更改默认情况所在的代码流,因为值 2 实际上应该执行默认情况。
    因此,编译器创建 tableswitch 时,开关不必非常紧凑,但它至少应该非常接近紧凑。现在考虑以下开关:
    switch (inputValue) {
      case 1:    // ...
      case 10:   // ...
      case 100:  // ...
      case 1000: // ...
      default:   // ...
    }
    
    这个开关远非紧凑,它的孔比值多一百倍。人们将其称为稀疏开关。编译器必须生成 近千件假货将此开关表示为 tableswitch。结果将是一个巨大的表,极大地增加了类文件的大小。这是不实用的。相反,它会生成一个查找开关:
    lookupswitch
        1       : Label1
        10      : Label10
        100     : Label100
        1000    : Label1000
        default : DefaultLabel
    
    这个表只有5个条目,而不是一千多个。该表有 4 个实数值,O(log 4) 是 2(这里的 log 是以 2 BTW 为底的,而不是以 10 为底的,因为计算机对二进制数进行运算)。这意味着 VM 最多需要两次比较才能找到 inputValue 的标签或得出结论,该值不在表中,因此必须执行默认值。即使该表有 100 个条目,VM 最多也需要 7 次比较才能找到正确的标签或决定跳转到默认标签(并且 7 次比较比 100 次比较少很多,你不觉得吗?)。
    所以说这两条指令可以互换或者说两条指令的原因有历史原因都是无稽之谈。对于两种不同的情况有两种指令,一种用于具有紧凑值的开关(用于最大速度),另一种用于具有稀疏值的开关(不是最大速度,但仍然具有良好的速度和非常紧凑的表格表示,无论数字孔如何)。

    关于java - JVM 的 LookupSwitch 和 TableSwitch 的区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10287700/

    有关java - JVM 的 LookupSwitch 和 TableSwitch 的区别?的更多相关文章

    1. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    2. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

      请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

    3. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

      我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

    4. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

      我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

    5. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    6. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    8. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

      这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

    9. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

      HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

    10. 【Java入门】使用Java实现文件夹的遍历 - 2

      遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

    随机推荐