使用按位运算或数字文字来初始化值是否更快?
Is it faster to initialize a value with a bitwise operation or a numeric literal?
我正在浏览 HashMap
的源代码。我看到了类似的东西
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16.
我想知道他们为什么使用移位运算符。这会加快计算速度吗?所以我寻找了这三个操作之间的 byte
差异:
int DEFAULT_INITIAL_CAPACITY = 0x10;
L0
LINENUMBER 52 L0
BIPUSH 16
ISTORE 1
int DEFAULT_INITIAL_CAPACITY1 = 1 << 4;
L1
LINENUMBER 54 L1
BIPUSH 16
ISTORE 2
int test = 16;
L2
LINENUMBER 56 L2
BIPUSH 16
ISTORE 3
值的初始化方式重要吗?
从速度的角度来看,这可能不是优势。然而,看起来 HashMap 的 capacity
总是 2 的幂(即使在构造函数中指定容量也会导致调用 Collections.roundUpToPowerOfTwo(capacity)
),因此以 1 << x
的形式表示它确保这个限制很简单,即使你要改变 x
。如果您不知道限制,其他表格在更改时会更容易搞砸
正如您自己发现的,字节码对于常量 16 或 1 << 4 是相同的。在这种特殊情况下,我认为这只是一个可读性问题:强调初始容量应该是 2 的幂(通过将 1 向左移动,您只能获得 2 的幂)。这是我在 HashMap 的源代码中的内容:
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
信不信由你,这实际上是关于可读性的。表达式 1 << 4
的计算速度肯定不会比表达式 16
快。另外,无论表达式是什么,它都会在编译时求值。
使用移位表示的要点是它是一种更自然的表示循环二进制数的方式。初始容量的不变性,以及哈希表实现中的许多其他事物,是它必须是 2 的纯幂。与十进制表示相比,表达式 1 << n
(相当于 2n)更直接地传达了这一点,尤其是当您进入 n
的更高值时(例如例如,任何高于 16 的值)。
下面是对你问题中描述的三种初始化方法的定时测试。
public static void main(String[] args) {
long time = System.currentTimeMillis();
int test = 0;
for(int i = 0; i < 100000; i++){
test = 16;
}
System.out.println((System.currentTimeMillis() - time) + " : " + test);
time = System.currentTimeMillis();
int test2 = 0;
for(int i = 0; i < 100000; i++){
test2 = 1 << 4;
}
System.out.println((System.currentTimeMillis() - time) + " : " + test2);
time = System.currentTimeMillis();
int test3 = 0;
for(int i = 0; i < 100000; i++){
test3 = 0x10;
}
System.out.println((System.currentTimeMillis() - time) + " : " + test3);
}
运行 这会产生
2 : 16
2 : 16
2 : 16
每次执行的时间为 + 或 - 5 毫秒。这表明值的初始化方式几乎无关紧要。
结论:
使用哪种方法初始化值没有程序化区别。
似乎使用 1 << 4
而不是 16
或 0x10
的唯一原因是强制初始值是 2 的幂。
基本上,移位运算符用于处理位,并且比处理 +、- 等其他运算符更快
例子-
将两个数字相乘,CPU 会在内部执行乘法运算,将其替换为加法和减法,它只不过是对位进行移位和执行 AND、OR、NOT 等运算。
因此,如果您直接在位上工作,则意味着您正在做 CPU 经过大量处理后必须完成的工作。
另见:
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
我正在浏览 HashMap
的源代码。我看到了类似的东西
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16.
我想知道他们为什么使用移位运算符。这会加快计算速度吗?所以我寻找了这三个操作之间的 byte
差异:
int DEFAULT_INITIAL_CAPACITY = 0x10;
L0
LINENUMBER 52 L0
BIPUSH 16
ISTORE 1
int DEFAULT_INITIAL_CAPACITY1 = 1 << 4;
L1
LINENUMBER 54 L1
BIPUSH 16
ISTORE 2
int test = 16;
L2
LINENUMBER 56 L2
BIPUSH 16
ISTORE 3
值的初始化方式重要吗?
从速度的角度来看,这可能不是优势。然而,看起来 HashMap 的 capacity
总是 2 的幂(即使在构造函数中指定容量也会导致调用 Collections.roundUpToPowerOfTwo(capacity)
),因此以 1 << x
的形式表示它确保这个限制很简单,即使你要改变 x
。如果您不知道限制,其他表格在更改时会更容易搞砸
正如您自己发现的,字节码对于常量 16 或 1 << 4 是相同的。在这种特殊情况下,我认为这只是一个可读性问题:强调初始容量应该是 2 的幂(通过将 1 向左移动,您只能获得 2 的幂)。这是我在 HashMap 的源代码中的内容:
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
信不信由你,这实际上是关于可读性的。表达式 1 << 4
的计算速度肯定不会比表达式 16
快。另外,无论表达式是什么,它都会在编译时求值。
使用移位表示的要点是它是一种更自然的表示循环二进制数的方式。初始容量的不变性,以及哈希表实现中的许多其他事物,是它必须是 2 的纯幂。与十进制表示相比,表达式 1 << n
(相当于 2n)更直接地传达了这一点,尤其是当您进入 n
的更高值时(例如例如,任何高于 16 的值)。
下面是对你问题中描述的三种初始化方法的定时测试。
public static void main(String[] args) {
long time = System.currentTimeMillis();
int test = 0;
for(int i = 0; i < 100000; i++){
test = 16;
}
System.out.println((System.currentTimeMillis() - time) + " : " + test);
time = System.currentTimeMillis();
int test2 = 0;
for(int i = 0; i < 100000; i++){
test2 = 1 << 4;
}
System.out.println((System.currentTimeMillis() - time) + " : " + test2);
time = System.currentTimeMillis();
int test3 = 0;
for(int i = 0; i < 100000; i++){
test3 = 0x10;
}
System.out.println((System.currentTimeMillis() - time) + " : " + test3);
}
运行 这会产生
2 : 16
2 : 16
2 : 16
每次执行的时间为 + 或 - 5 毫秒。这表明值的初始化方式几乎无关紧要。
结论:
使用哪种方法初始化值没有程序化区别。
似乎使用 1 << 4
而不是 16
或 0x10
的唯一原因是强制初始值是 2 的幂。
基本上,移位运算符用于处理位,并且比处理 +、- 等其他运算符更快
例子- 将两个数字相乘,CPU 会在内部执行乘法运算,将其替换为加法和减法,它只不过是对位进行移位和执行 AND、OR、NOT 等运算。
因此,如果您直接在位上工作,则意味着您正在做 CPU 经过大量处理后必须完成的工作。
另见: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html