亚洲全黄无码一级在线看_国产剧情久久久性色_无码av一区二区三区无码_亚洲成a×人片在线观看

當(dāng)前位置: 首頁(yè) > 軍事新聞 >

Android 上哪個(gè)更好:除以 2 還是位移 1?

時(shí)間:2020-07-22 17:32來(lái)源:網(wǎng)絡(luò)整理 瀏覽:
作者 | Jake Wharton譯者 | 孫薇,責(zé)編 | 夕顏頭圖 | CSDN下載自視覺(jué)中國(guó)出品 | CSDN(ID:CSDNnews)
Android 上哪個(gè)更好:除以 2 還是位移 1?

作者 | Jake Wharton

譯者 | 孫薇,責(zé)編 | 夕顏

頭圖 | CSDN下載自視覺(jué)中國(guó)

出品 | CSDN(ID:CSDNnews)

以下為譯文:

我一直在將AndroidX集合庫(kù)移植到Kotlin multiplatform上,以試驗(yàn)二進(jìn)制兼容性、性能、工具以及不同的內(nèi)存模型。庫(kù)中的某些數(shù)據(jù)結(jié)構(gòu)使用了基于數(shù)組的二叉樹(shù)來(lái)存儲(chǔ)元素。Java代碼中有大量位移來(lái)替代2的冪的乘除法。當(dāng)移植到Kotlin之后,這些就成了略微有點(diǎn)別扭的中綴運(yùn)算符,導(dǎo)致代碼意圖進(jìn)一步被混淆。

我找了些人來(lái)調(diào)查對(duì)按位移位(bitwise shifts)與乘除法的看法,很多人聽(tīng)說(shuō)過(guò)移位性能更好的傳聞,但每個(gè)人對(duì)其真實(shí)性仍持懷疑態(tài)度。一些人認(rèn)為,代碼在CPU上運(yùn)行之前所見(jiàn)過(guò)的一個(gè)編譯器可用來(lái)優(yōu)化這個(gè)案例。

為了滿足我的好奇心(部分也是為了避免Kotlin的中綴按位運(yùn)算符),我打算回答哪個(gè)更優(yōu)的問(wèn)題,以及一些相關(guān)的問(wèn)題。那么這就開(kāi)始吧。

Android 上哪個(gè)更好:除以 2 還是位移 1?

有人優(yōu)化嗎?

在代碼進(jìn)入CPU之前,主要經(jīng)過(guò)三個(gè)編譯器:`javac`/`kotlinc`,D8/R8,以及ART。

Android 上哪個(gè)更好:除以 2 還是位移 1?

它們都有機(jī)會(huì)對(duì)代碼進(jìn)行優(yōu)化,但它們會(huì)這樣做嗎?

javac
class Example { static int multiply(int value) { return value * 2; } static int divide(int value) { return value / 2; } static int shiftLeft(int value) { return value << 1; } static int shiftRight(int value) { return value >> 1; }}

可以使用JDK14中的javac來(lái)編譯這段Java代碼,并通過(guò)javap來(lái)顯示生成的字節(jié)碼。

$ javac Example.java$ javap -c ExampleCompiled from "Example.java"class Example { static int multiply(int); Code: 0: iload_0 1: iconst_2 2: imul 3: ireturn

static int divide(int); Code: 0: iload_0 1: iconst_2 2: idiv 3: ireturn

static int shiftLeft(int); Code: 0: iload_0 1: iconst_1 2: ishl 3: ireturn

static int shiftRight(int); Code: 0: iload_0 1: iconst_1 2: ishr 3: ireturn

以 `iload_0` 開(kāi)頭的每個(gè)方法會(huì)加載第一個(gè)實(shí)參值,之后乘法和除法都包含 `iconst_2` ,它們加載常量2,然后分別運(yùn)行 `imul` 或者 `idiv` ,以執(zhí)行整數(shù)乘法或整數(shù)除法。移位方法在`ishl`或`ishr`之前加載常量1,分別執(zhí)行整數(shù)向左移位和整數(shù)向右移位。

這里沒(méi)有優(yōu)化,但如果你有對(duì)Java有所了解,就知道這并不意外。`javac`并不是一個(gè)優(yōu)化編譯器,它將大部分工作留給了JVM上的運(yùn)行時(shí)編譯器或者提前編譯器。

kotlinc
fun multiply(value: Int) = value * 2fun divide(value: Int) = value / 2fun shiftLeft(value: Int) = value shl 1fun shiftRight(value: Int) = value shr 1

使用Kotlin 1.4-M1中的`Kotlinc`將Kotlin編譯為Java字節(jié)碼,這樣`javap`工具就能再次使用。

$ kotlinc Example.kt$ javap -c ExampleKtCompiled from "Example.kt"public final class ExampleKt { public static final int multiply(int); Code: 0: iload_0 1: iconst_2 2: imul 3: ireturn

public static final int divide(int); Code: 0: iload_0 1: iconst_2 2: idiv 3: ireturn

public static final int shiftLeft(int); Code: 0: iload_0 1: iconst_1 2: ishl 3: ireturn

public static final int shiftRight(int); Code: 0: iload_0 1: iconst_1 2: ishr 3: ireturn

與Java的輸出結(jié)果完全一致。這是運(yùn)用了Kotlin原始的JVM后端,但使用基于IR的后端(通過(guò)`-Xuse-ir`)也能產(chǎn)生同樣的輸出。

D8

我們使用Kotlin示例中的Java字節(jié)碼輸出作為由 `master`(在文本撰寫(xiě)時(shí)是SHA `2a2bf622d`)所構(gòu)建的最新D8的輸入。

$ java -jar $R8_HOME/build/libs/d8.jar \ --release \ --output . \ ExampleKt.class$ dexdump -d classes.dexOpened 'classes.dex', DEX version '035'Class #0 - Class descriptor : 'LExampleKt;' Access flags : 0x0011 (PUBLIC FINAL) Superclass : 'Ljava/lang/Object;' Direct methods - #0 : (in LExampleKt;) name : 'divide' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000118: |[000118] ExampleKt.divide:(I)I000128: db00 0102 |0000: div-int/lit8 v0, v1, #int 2 // #0200012c: 0f00 |0002: return v0

#1 : (in LExampleKt;) name : 'multiply' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000130: |[000130] ExampleKt.multiply:(I)I000140: da00 0102 |0000: mul-int/lit8 v0, v1, #int 2 // #02000144: 0f00 |0002: return v0

#2 : (in LExampleKt;) name : 'shiftLeft' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000148: |[000148] ExampleKt.shiftLeft:(I)I000158: e000 0101 |0000: shl-int/lit8 v0, v1, #int 1 // #0100015c: 0f00 |0002: return v0

#3 : (in LExampleKt;) name : 'shiftRight' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000160: |[000160] ExampleKt.shiftRight:(I)I000170: e100 0101 |0000: shr-int/lit8 v0, v1, #int 1 // #01000174: 0f00 |0002: return

(注:輸出略微修剪)

Dalvik字節(jié)碼是基于寄存器的,而不是像Java字節(jié)碼那樣基于堆棧。因此,每種方法只有一個(gè)實(shí)字節(jié)碼來(lái)執(zhí)行相關(guān)的整數(shù)運(yùn)算。每個(gè)寄存器都使用v1寄存器,也是第一個(gè)實(shí)參值,以及2或1的整型常量。

因此沒(méi)有更改行為,但D8也不是一個(gè)優(yōu)化編輯器(盡管它可以執(zhí)行局部方法的優(yōu)化)。

R8

要運(yùn)行R8,我們需要定義一項(xiàng)規(guī)則,以防止我們的方法被刪除。

-keep,allowoptimization class ExampleKt { <methods>;}

這些規(guī)則通過(guò) `--pg-conf` 來(lái)傳遞,我們還提供了Android API來(lái)鏈接使用 `--lib`。

$ java -jar $R8_HOME/build/libs/r8.jar \ --lib $ANDROID_HOME/platforms/android-29/android.jar \ --release \ --pg-conf rules.txt \ --output . \ ExampleKt.class$ dexdump -d classes.dexOpened 'classes.dex', DEX version '035'Class #0 - Class descriptor : 'LExampleKt;' Access flags : 0x0011 (PUBLIC FINAL) Superclass : 'Ljava/lang/Object;' Direct methods - #0 : (in LExampleKt;) name : 'divide' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000118: |[000118] ExampleKt.divide:(I)I000128: db00 0102 |0000: div-int/lit8 v0, v1, #int 2 // #0200012c: 0f00 |0002: return v0

#1 : (in LExampleKt;) name : 'multiply' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000130: |[000130] ExampleKt.multiply:(I)I000140: da00 0102 |0000: mul-int/lit8 v0, v1, #int 2 // #02000144: 0f00 |0002: return v0

#2 : (in LExampleKt;) name : 'shiftLeft' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000148: |[000148] ExampleKt.shiftLeft:(I)I000158: e000 0101 |0000: shl-int/lit8 v0, v1, #int 1 // #0100015c: 0f00 |0002: return v0

#3 : (in LExampleKt;) name : 'shiftRight' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000160: |[000160] ExampleKt.shiftRight:(I)I000170: e100 0101 |0000: shr-int/lit8 v0, v1, #int 1 // #01000174: 0f00 |0002: return

與D8的輸出完全相同。

ART

我們使用R8示例中的Dalvik字節(jié)碼輸出,作為在x86模擬器上的Android 10系統(tǒng)運(yùn)行的ART的輸入。

$ adb push classes.dex /sdcard/classes.dex$ adb shellgeneric_x86:/ $ sugeneric_x86:/ # dex2oat --dex-file=/sdcard/classes.dex --oat-file=/sdcard/classes.oatgeneric_x86:/ # oatdump --oat-file=/sdcard/classes.oatOatDexFile:0: LExampleKt; (offset=0x000003c0) (type_idx=1) (Initialized) (OatClassAllCompiled) 0: int ExampleKt.divide(int) (dex_method_idx=0) CODE: (code_offset=0x00001010 size_offset=0x0000100c size=15)... 0x00001010: 89C8 mov eax, ecx 0x00001012: 8D5001 lea edx, [eax + 1] 0x00001015: 85C0 test eax, eax 0x00001017: 0F4DD0 cmovnl/ge edx, eax 0x0000101a: D1FA sar edx 0x0000101c: 89D0 mov eax, edx 0x0000101e: C3 ret 1: int ExampleKt.multiply(int) (dex_method_idx=1) CODE: (code_offset=0x00001030 size_offset=0x0000102c size=5)... 0x00001030: D1E1 shl ecx 0x00001032: 89C8 mov eax, ecx 0x00001034: C3 ret 2: int ExampleKt.shiftLeft(int) (dex_method_idx=2) CODE: (code_offset=0x00001030 size_offset=0x0000102c size=5)... 0x00001030: D1E1 shl ecx 0x00001032: 89C8 mov eax, ecx 0x00001034: C3 ret 3: int ExampleKt.shiftRight(int) (dex_method_idx=3) CODE: (code_offset=0x00001040 size_offset=0x0000103c size=5)... 0x00001040: D1F9 sar ecx 0x00001042: 89C8 mov eax, ecx 0x00001044: C3 ret
 (注意:輸出有大幅修剪)

x86匯編顯示,ART確實(shí)介入并規(guī)范化了算術(shù)運(yùn)算符以使用移位。

首先,現(xiàn)在`multiply`和`shiftLeft的`實(shí)現(xiàn)完全一致。它們都使用`shl`來(lái)執(zhí)行向左按位移位1,此外如果查看文件夾中的偏移量(最左邊的列),會(huì)發(fā)現(xiàn)它們是完全一致的。ART認(rèn)識(shí)到這些函數(shù)在編譯到x86匯編時(shí)具有相同的主體,并已經(jīng)刪除了重復(fù)的數(shù)據(jù)。

下一步,盡管`divide`和`shiftRight`不一樣,其對(duì)`sar`的用法是一樣的,都是向右按位移位1,在`divide`中的四條附加指令通過(guò)給值加1,先于`sar`處理輸入為負(fù)的情況(注釋1)。

在Android 10系統(tǒng)的Pixel 4上運(yùn)行相同的命令,會(huì)顯示ART如何將此代碼編譯到ARM匯編中(注釋2)。

OatDexFile:0: LExampleKt; (offset=0x000005a4) (type_idx=1) (Verified) (OatClassAllCompiled) 0: int ExampleKt.divide(int) (dex_method_idx=0) CODE: (code_offset=0x00001009 size_offset=0x00001004 size=10)... 0x00001008: 0fc8 lsrs r0, r1, #31 0x0000100a: 1841 adds r1, r0, r1 0x0000100c: 1049 asrs r1, #1 0x0000100e: 4608 mov r0, r1 0x00001010: 4770 bx lr 1: int ExampleKt.multiply(int) (dex_method_idx=1) CODE: (code_offset=0x00001021 size_offset=0x0000101c size=4)... 0x00001020: 0048 lsls r0, r1, #1 0x00001022: 4770 bx lr 2: int ExampleKt.shiftLeft(int) (dex_method_idx=2) CODE: (code_offset=0x00001021 size_offset=0x0000101c size=4)... 0x00001020: 0048 lsls r0, r1, #1 0x00001022: 4770 bx lr 3: int ExampleKt.shiftRight(int) (dex_method_idx=3) CODE: (code_offset=0x00001031 size_offset=0x0000102c size=4)... 0x00001030: 1048 asrs r0, r1, #1 0x00001032: 4770 bx lr

同樣,`multiply`和`shiftLeft`都使用`lsls`來(lái)執(zhí)行向左位移,因此被去重了。`shiftRight`用`asrs`來(lái)執(zhí)行向右位移。`divide`也使用`asrs`來(lái)執(zhí)行向右位移,但它使用另一個(gè)向右位移`lsrs`來(lái)處理負(fù)值加一的操作(注釋3)。

這樣一來(lái),我們現(xiàn)在可以肯定地說(shuō),用`value << 1`來(lái)替代`value * 2`沒(méi)有任何好處,不要再為了算術(shù)運(yùn)算而這么做了,僅保留用于嚴(yán)格的按位運(yùn)算。

然而,`value / 2` 和`value >> 1`仍會(huì)產(chǎn)生不同的匯編指令,因而可能具有不同的性能特征。幸運(yùn)的是,使用`value / 2`可避免使用通用除法,并且仍舊主要基于向右位移,因此它們?cè)谛阅芊矫娌町惪赡懿淮蟆?/p>

位移會(huì)比除法快一些嗎?

為了確定除法快還是位移更快,我們可以使用Jetpack benchmark庫(kù)。

class DivideOrShiftTest { @JvmField @Rule val benchmark = BenchmarkRule

@Test fun divide { val value = "4".toInt // Ensure not a constant. var result = 0 benchmark.measureRepeated { result = value / 2 } println(result) // Ensure D8 keeps computation. }

@Test fun shift { val value = "4".toInt // Ensure not a constant. var result = 0 benchmark.measureRepeated { result = value shr 1 } println(result) // Ensure D8 keeps computation. }

我沒(méi)有x86設(shè)備,但有一臺(tái)運(yùn)行Android 10系統(tǒng)的基于ARM的Pixel 3,結(jié)果如下:

android.studio.display.benchmark=4 ns DivideOrShiftTest.dividecount=4006mean=4median=4min=4standardDeviation=0

android.studio.display.benchmark=3 ns DivideOrShiftTest.shiftcount=3943mean=3median=3min=3standardDeviation=0

對(duì)如此小的數(shù)字使用除法或位移之間的區(qū)別幾近于無(wú),畢竟差異太小了。使用負(fù)數(shù)顯示結(jié)果并無(wú)差異。

這樣一來(lái),我們現(xiàn)在可以肯定,用`value >> 1`代替`value / 2`并無(wú)好處,不要再為算術(shù)運(yùn)算這么做了,僅保留用于嚴(yán)格的按位運(yùn)算。

D8/R8能用這些信息來(lái)保存APK大小嗎?

針對(duì)兩個(gè)操作相同的表達(dá)方式,我們應(yīng)該選擇性能更佳的。但如果兩者性能相同的話,則應(yīng)選擇APK更小的。

我們知道,ART中`value * 2`和`value << 1`會(huì)產(chǎn)生相同的匯編,因此如果在Dalvik字節(jié)碼中,一個(gè)比另一個(gè)更省空間,我們應(yīng)該無(wú)條件地以更小的形式將其重寫(xiě)。查看D8中的輸出,它們產(chǎn)生的字節(jié)碼大小相同:

 #1 : (in LExampleKt;) name : 'multiply' ?000140: da00 0102 |0000: mul-int/lit8 v0, v1, #int 2 // #02

#2 : (in LExampleKt;) name : 'shiftLeft' ?000158: e000 0101 |0000: shl-int/lit8 v0, v1, #int 1 // #01

盡管使用2的冪沒(méi)有收益,但在移位以存儲(chǔ)常量值之前,乘法用完了字節(jié)碼空間,下面是`value * 32_768`與`value << 15`的對(duì)比:

 #1 : (in LExampleKt;) name : 'multiply' ?000128: 1400 0080 0000 |0000: const v0, #float 0.000000 // #0000800000012e: 9201 0100 |0003: mul-int v1, v1, v0

#2 : (in LExampleKt;) name : 'shiftLeft' ?00015c: e000 000f |0000: shl-int/lit8 v0, v0, #int 15 // #0f

我在D8上提了個(gè)問(wèn)題以調(diào)查如何自動(dòng)優(yōu)化此問(wèn)題,但我強(qiáng)烈懷疑這種適用情況接近于零,因此很可能并不值得。

D8和R8的輸出也告訴我們,在Dalvik字節(jié)碼方面,`value / 2`和`value >> 1`的代價(jià)是相同的。

 #0 : (in LExampleKt;) name : 'divide' ?000128: db00 0102 |0000: div-int/lit8 v0, v1, #int 2 // #02

#2 : (in LExampleKt;) name : 'shiftLeft' ?000158: e000 0101 |0000: shl-int/lit8 v0, v1,#int 1 // #01

當(dāng)常量達(dá)到32768時(shí),其字節(jié)碼大小也會(huì)有所不同。無(wú)條件地將2的冪除法換成向右位移永遠(yuǎn)不是安全的選項(xiàng),這是因?yàn)樨?fù)數(shù)的存在。如果能保證其值非負(fù),那我們可以這樣替換,但此時(shí)D8和R8并不會(huì)追蹤可能的整數(shù)值范圍。

無(wú)符號(hào)的“2的冪除法”使用位移嗎?

Java字節(jié)碼缺少無(wú)符號(hào)的數(shù)字,但使用符號(hào)的對(duì)應(yīng)部分是可以模擬的。在Java中,有一些將符號(hào)類型當(dāng)作無(wú)符號(hào)值運(yùn)算的靜態(tài)輔助方法。Kotlin提供了類似 `UInt` 這樣的類型完成類似的功能,但在類型后完全抽象了??梢韵胂蟮氖牵?dāng)使用除以2的冪時(shí),應(yīng)當(dāng)以移位方式重寫(xiě)。

我們可以使用Kotlin來(lái)為兩種情況建模。

fun javaLike(value: Int) = Integer.divideUnsigned(value, 2)fun kotlinLike(value: UInt) = value / 2U

在某些情況下,僅需考慮代碼的編譯方式。我們從普通的 `kotlinc`開(kāi)始(還是從Kotlin 1.4-M1開(kāi)始)。

$ kotlinc Example.kt$ javap -c ExampleKtCompiled from "Example.kt"public final class ExampleKt { public static final int javaLike(int); Code: 0: iload_0 1: iconst_2 2: invokestatic #12 // Method java/lang/Integer.divideUnsigned:(II)I 5: ireturn

public static final int kotlinLike-WZ4Q5Ns(int); Code: 0: iload_0 1: istore_1 2: iconst_2 3: istore_2 4: iconst_0 5: istore_3 6: iload_1 7: iload_2 8: invokestatic #20 // Method kotlin/UnsignedKt."uintDivide-J1ME1BU":(II)I 11: ireturn}

Kotlin無(wú)法將其識(shí)別為可使用`iushr`字節(jié)碼的“2的冪除法”,我提交了KT-38493以追蹤此行為的添加。

使用`-Xuse-ir`不會(huì)有任何改變(除非移除某些負(fù)載/存儲(chǔ)噪音),然而以Java 8為目標(biāo)則會(huì)有變化。

$ kotlinc -jvm-target 1.8 Example.kt$ javap -c ExampleKtCompiled from "Example.kt"public final class ExampleKt { public static final int javaLike(int); Code: 0: iload_0 1: iconst_2 2: invokestatic #12 // Method java/lang/Integer.divideUnsigned:(II)I 5: ireturn

public static final int kotlinLike-WZ4Q5Ns(int); Code: 0: iload_0 1: iconst_2 2: invokestatic #12 // Method java/lang/Integer.divideUnsigned:(II)I 5: ireturn}

`Integer.divideUnsigned`方法在Java 8中可以使用,因此在1.8或更高版本中較多使用,由于這會(huì)使得兩個(gè)函數(shù)體完全相同,我們還是返回舊輸出,對(duì)比看看會(huì)發(fā)生什么。

接下來(lái)是R8,與上面調(diào)用明顯不同,我們將Kotlin stdlib作為輸入引入,同時(shí)由于 `Integer.divideUnsigned` 僅在API 24和更高版本中可用,也傳遞了`--min-api 24`。

$ java -jar $R8_HOME/build/libs/r8.jar \ --lib $ANDROID_HOME/platforms/android-29/android.jar \ --min-api 24 \ --release \ --pg-conf rules.txt \ --output . \ ExampleKt.class kotlin-stdlib.jar$ dexdump -d classes.dexOpened 'classes.dex', DEX version '039'Class #0 - Class descriptor : 'LExampleKt;' Access flags : 0x0011 (PUBLIC FINAL) Superclass : 'Ljava/lang/Object;' Direct methods - #0 : (in LExampleKt;) name : 'javaLike' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -0000f8: |[0000f8] ExampleKt.javaLike:(I)I000108: 1220 |0000: const/4 v0, #int 2 // #200010a: 7120 0200 0100 |0001: invoke-static {v1, v0}, Ljava/lang/Integer;.divideUnsigned:(II)I // method@0002000110: 0a01 |0004: move-result v1000112: 0f01 |0005: return v1

#1 : (in LExampleKt;) name : 'kotlinLike-WZ4Q5Ns' type : '(I)I' access : 0x0019 (PUBLIC STATIC FINAL) code -000114: |[000114] ExampleKt.kotlinLike-WZ4Q5Ns:(I)I000124: 8160 |0000: int-to-long v0, v6000126: 1802 ffff ffff 0000 0000 |0001: const-wide v2, #double 0.000000 // #00000000ffffffff000130: c020 |0006: and-long/2addr v0, v2000132: 1226 |0007: const/4 v6, #int 2 // #2000134: 8164 |0008: int-to-long v4, v6000136: c042 |0009: and-long/2addr v2, v4000138: be20 |000a: div-long/2addr v0, v200013a: 8406 |000b: long-to-int v6, v000013c: 0f06 |000c: return v6

Kotlin有自己的無(wú)符號(hào)整數(shù)除法的實(shí)現(xiàn)方式,已經(jīng)與我們的函數(shù)內(nèi)聯(lián)。它會(huì)將輸入實(shí)參和常量轉(zhuǎn)化為longs,執(zhí)行l(wèi)ongs除法,然后再轉(zhuǎn)回整數(shù)。當(dāng)我們最終通過(guò)ART來(lái)運(yùn)行時(shí),它們會(huì)轉(zhuǎn)成等效的x86,因此我們將保留此函數(shù),這里優(yōu)化的機(jī)會(huì)已經(jīng)失去了。

對(duì)于Java版本,R8無(wú)法將`divideUnsigned`調(diào)用替換成位移,我針對(duì)D8和R8提交了issue 154712996來(lái)跟蹤這個(gè)問(wèn)題。

最后一個(gè)優(yōu)化的機(jī)會(huì)是ART。

$ adb push classes.dex /sdcard/classes.dex$ adb shellgeneric_x86:/ $ sugeneric_x86:/ # dex2oat --dex-file=/sdcard/classes.dex --oat-file=/sdcard/classes.oatgeneric_x86:/ # oatdump --oat-file=/sdcard/classes.oatOatDexFile:0: LExampleKt; (offset=0x000003c0) (type_idx=1) (Initialized) (OatClassAllCompiled) 0: int ExampleKt.javaLike(int) (dex_method_idx=0) CODE: (code_offset=0x00001010 size_offset=0x0000100c size=63)... 0x00001010: 85842400E0FFFF test eax, [esp + -8192] StackMap[0] (native_pc=0x1017, dex_pc=0x0, register_mask=0x0, stack_mask=0b) 0x00001017: 55 push ebp 0x00001018: 83EC18 sub esp, 24 0x0000101b: 890424 mov [esp], eax 0x0000101e: 6466833D0000000000 cmpw fs:[0x0], 0 ; state_and_flags 0x00001027: 0F8519000000 jnz/ne +25 (0x00001046) 0x0000102d: E800000000 call +0 (0x00001032) 0x00001032: 5D pop ebp 0x00001033: BA02000000 mov edx, 2 0x00001038: 8B85CE0F0000 mov eax, [ebp + 4046] 0x0000103e: FF5018 call [eax + 24] StackMap[1] (native_pc=0x1041, dex_pc=0x1, register_mask=0x0, stack_mask=0b) 0x00001041: 83C418 add esp, 24 0x00001044: 5D pop ebp 0x00001045: C3 ret 0x00001046: 64FF15E0020000 call fs:[0x2e0] ; pTestSuspend StackMap[2] (native_pc=0x104d, dex_pc=0x0, register_mask=0x0, stack_mask=0b) 0x0000104d: EBDE jmp -34 (0x0000102d) 1: int ExampleKt.kotlinLike-WZ4Q5Ns(int) (dex_method_idx=1) CODE: (code_offset=0x00001060 size_offset=0x0000105c size=67)... ?

ART內(nèi)化調(diào)用 `divideUnsigned`,因此我們讓機(jī)器跳至常規(guī)方法的實(shí)現(xiàn)。我提交了issue 154693569以跟蹤為無(wú)符號(hào)除法添加ART內(nèi)化的問(wèn)題。

好吧,這確實(shí)費(fèi)了不少勁。恭喜你到達(dá)此步(或只是快速翻到這里),我們總結(jié)一下:

ART將2的冪乘法重寫(xiě)為向左位移,將2的冪除法重寫(xiě)為向右位移(通過(guò)一些額外的指令來(lái)處理負(fù)數(shù));

向右位移和2的冪除法之間沒(méi)有明顯的性能差異;

Dalvik字節(jié)碼中,位移和乘法/除法之間沒(méi)有大小差異;

截至目前還沒(méi)有人優(yōu)化無(wú)簽名除法,但也許你也不會(huì)用到。

通過(guò)以上事實(shí),我們可以回答本文題目中提出的問(wèn)題了:

Android上哪個(gè)更好:除以2還是位移1?

都不好!因此將除法用于算術(shù)運(yùn)算中,對(duì)于真實(shí)的按位操作只使用位移,我會(huì)將AndroidX集合端口從位移切換到乘除法。下次見(jiàn)!

注釋:

1. 二進(jìn)制中的-3為0b11111101,如果我們嘗試僅向右位移來(lái)實(shí)現(xiàn)除以2,結(jié)果是0b11111110,即-2,這是錯(cuò)誤的結(jié)果。通過(guò)給-3加1,首先我們會(huì)得到-2,二進(jìn)制表達(dá)是0b11111110,向右位移則得出0b11111111,也就是-1,這是正確的結(jié)果。

根據(jù)實(shí)際中的指令:

`mov eax, ecx` 將原始輸入實(shí)參值保存在`eax`中;

`lea edx, [eax + 1]` 給輸入實(shí)參加1,并將結(jié)果存儲(chǔ)在`edx`中,即我們要位移的寄存器;

`test eax, eax`對(duì)自身的輸入實(shí)參進(jìn)行按位操作,導(dǎo)致一些寄存器會(huì)根據(jù)輸入實(shí)參的屬性來(lái)設(shè)置;

之后`cmovnl/ge edx, eax`可能會(huì)基于`test`的結(jié)果,以`eax` (值)重寫(xiě)`edx` (值+1)。

之后指令會(huì)執(zhí)行普通的向右位移,與 `(value < 0 ? value + 1 : value) >> 1`. ?基本相同。

2. 感謝Sergey Vasilinets提供的相關(guān)內(nèi)容,`dex2oat`只能在現(xiàn)代Android版本中以root身份來(lái)運(yùn)行,因此普通的Android(如在Pixel3上的)就無(wú)法運(yùn)行。?

3. 就實(shí)際指令而言:

`lsrs r0, r1, #31` 對(duì)輸入實(shí)參進(jìn)行邏輯(即不對(duì)符號(hào)進(jìn)行擴(kuò)展)31位的位移到`r0`,導(dǎo)致負(fù)數(shù)結(jié)果為1,正數(shù)結(jié)果為0。

`adds r1, r0, r1` 會(huì)將前一條指令的結(jié)果與輸入實(shí)參相加,實(shí)際上是給負(fù)值輸入加1。

自此指令會(huì)執(zhí)行普通的向右位移,基本上等同于`(value + (value >>> 31)) >> 1`。

原文鏈接:

https://jakewharton.com/which-is-better-on-android-divide-by-two-or-shift-by-one/

本文為CSDN翻譯文章,轉(zhuǎn)載請(qǐng)注明出處。

Android 上哪個(gè)更好:除以 2 還是位移 1?Android 上哪個(gè)更好:除以 2 還是位移 1?

?潘石屹 Python 考試成績(jī) 99 分,網(wǎng)友:還有一分怕你驕傲

?贈(zèng)書(shū) | 程序員修煉的務(wù)實(shí)哲學(xué)

?JavaScript 流行度最高,Java 屈居第三!| 2020 最新軟件開(kāi)發(fā)狀況報(bào)告

?深度學(xué)習(xí)基礎(chǔ)總結(jié),無(wú)一句廢話(附完整思維導(dǎo)圖)

?震驚!阿里的程序員竟被一個(gè)簡(jiǎn)單的 SQL 查詢難住了!

?大學(xué)生程序員被勒索比特幣后,絕地反擊!

推薦內(nèi)容