Core Java

Java Performance 2 * i * i Multiplication : 2 * (i * i) Faster Than 2 * i * i

When optimizing code, even small differences in expression syntax can impact performance. One such example is the difference between 2 * (i * i) and 2 * i * i in Java. At first glance, these two expressions might seem identical, but subtle differences in how they’re evaluated can lead to performance discrepancies. In this tutorial, we’ll explore why 2 * (i * i) is generally faster than 2 * i * i and dive into the underlying reasons. Let’s begin the tutorial looking at java performance 2 * i * i multiplication

1. Introduction

The efficiency of mathematical operations in programming can sometimes hinge on subtle differences in syntax and precedence. In Java, a particular point of interest arises when comparing expressions like 2 * (i * i) and 2 * i * i. At first glance, these expressions might seem functionally identical, but their performance can vary due to the way Java’s compiler and runtime environment optimize mathematical calculations.

Understanding why 2 * (i * i) can be faster than 2 * i * i involves delving into how Java handles arithmetic operations and the precedence rules governing them. Specifically, the placement of parentheses can affect the order in which operations are executed, potentially leading to more efficient computation. This topic is not just academic; it has practical implications for optimizing code performance, especially in applications where large-scale mathematical computations are routine. By exploring the underlying mechanics of Java’s operation handling, we can better appreciate how even small syntactic choices can influence the efficiency of our programs.

Now let us look at mathematical expressions in java.

2. Understanding the Expression

Understanding mathematical expressions in Java is fundamental for anyone working with this programming language, especially when performance and accuracy are critical. In Java, mathematical expressions are built using operators such as addition (+), subtraction (-), multiplication (*), division (/), and modulus (%). These operators follow the standard rules of precedence, meaning multiplication and division are performed before addition and subtraction unless parentheses are used to dictate the order of operations explicitly.

Java handles these expressions efficiently, but the exact performance can vary depending on how the expression is structured. For instance, subtle differences in using parentheses can influence the execution order, potentially speeding up computations in complex expressions. Understanding these nuances is crucial for writing optimized code that executes efficiently, particularly in applications that require heavy mathematical processing such as scientific computations, graphics rendering, or financial modelling.

Moreover, Java provides a robust library of mathematical functions within the java.lang.Math class, enabling more complex mathematical operations beyond basic arithmetic. Functions such as Math.sqrt, Math.pow, and Math.log can perform square root, power, and logarithmic calculations. Utilizing these built-in functions not only simplifies the code but also ensures accuracy and performance as the Java runtime environment optimizes them.

Overall, grasping the principles behind mathematical expressions and their execution in Java is essential for any developer looking to leverage the full power of this language. It allows for creating efficient, reliable, and high-performance applications, making it a cornerstone of effective Java programming. You need to have java installed after downloading from the java site.

2.1 Example – Check Performance Of The Two Expressions

Let us look at the Java code for the implementation:


public class ExpressionPerformanceExample
{

  public static void main(String[] args)
  {
     int i=2;
    long timeinnanos1 = System.nanoTime();
	int op1 = 0;
	for(int j=0; j< 1000000000; j++)
	{
      op1 +=  2 * i * i;
    }
	long timeinnanos2 = System.nanoTime();
	System.out.println("Time taken for op1 :"+ (timeinnanos2-timeinnanos1)+ " nanos");
	
	long timeinnanos3 = System.nanoTime();
	int op2 = 0;
	for(int k=0; k< 1000000000; k++)
	{
      op2 +=  2 * (i * i);
    }
	long timeinnanos4 = System.nanoTime();
	System.out.println("Time taken for op2 :"+ (timeinnanos4-timeinnanos3)+ " nanos");
  
  }

}

You can compile and execute the above code with the following commands:

 
javac ExpressionPerformanceExample.java
java ExpressionPerformanceExample

The output for the above commands when executed is shown below:

 
bhagvanarch@Bhagvans-MacBook-Air operation_Performance_3_nov % java ExpressionPerformanceExample
Time taken for op1 :46584958 nanos
Time taken for op2 :41458459 nanos
bhagvanarch@Bhagvans-MacBook-Air operation_Performance_3_nov %

3. Performance Comparison

When comparing the performance of 2 * (i * i) versus 2 * i * i in Java, the difference lies in the way the expressions are evaluated by the Java compiler and runtime. In 2 * (i * i), the parentheses force the multiplication i * i to be evaluated first, creating an intermediate result before multiplying by 2. This clear precedence can be beneficial for readability and may help the compiler optimize the calculation more efficiently, depending on the underlying hardware and Java Virtual Machine (JVM) optimizations.

On the other hand, 2 * i * i relies on the default operator precedence rules in Java, which dictate that multiplication operations are performed from left to right. Therefore, the expression is evaluated as (2 * i) * i. Although the end result is mathematically equivalent, the lack of parentheses means the compiler must rely on these rules to determine the order of operations, which might introduce slight overhead.

In most practical scenarios, especially with modern compilers and JVM optimizations, the performance difference between these two expressions is negligible. However, these small differences can add up for highly performance-sensitive applications or those involving intensive numerical computations. Developers might prefer the explicit use of parentheses in 2 * (i * i) to ensure clarity and possibly aid in slight performance gains, emphasizing readability and maintainability in the codebase. The choice between the two often comes down to coding standards and personal or team preferences for readability rather than significant performance differences.

In the last section, you can see in the output that the time taken for the expression 2*i*i is 46584958 nanos and for the expression 2 * (i * i) is 41458459 nanos. The performance difference is significant.

4. Performance Testing Using JMH

Performance testing is a critical aspect of software development, ensuring that applications meet the required speed, scalability, and stability. One powerful tool for performance testing in the Java ecosystem is the Java Microbenchmark Harness (JMH). Developed by the OpenJDK community, JMH is specifically designed to measure the performance of Java code with precision, allowing developers to understand and optimize their code’s efficiency.

JMH works by providing a framework that facilitates the creation, execution, and analysis of benchmarks. It allows developers to write simple Java methods to be tested, surrounded by annotations that define the benchmarking parameters. These parameters include specifying the number of iterations, warm-up periods, and measurement time, all of which are crucial for obtaining accurate and consistent results. JMH takes care of the boilerplate code and ensures that the benchmarks are executed in a controlled environment, minimizing the interference from external factors.

One of the key features of JMH is its ability to isolate the performance of microbenchmarks, which focus on small, critical sections of code. This level of granularity helps in identifying bottlenecks and inefficiencies that might be overlooked in broader performance tests. JMH also provides various modes for benchmarking, such as throughput, average time, and sample time, allowing developers to choose the most appropriate metric for their specific needs.

Moreover, JMH supports advanced features like parameterized benchmarks and benchmark modes, enabling comprehensive testing of different scenarios and configurations. The tool also integrates seamlessly with popular build systems like Maven and Gradle, making it easy to incorporate into existing development workflows. By using JMH, developers can gain deep insights into their code’s performance characteristics, leading to more informed optimization decisions and ultimately more performant applications.

JMH Framework : Performance Testing and Time Measurement

JMH Framework : Performance Testing and Time Measurement

Overall, JMH is an invaluable tool for Java developers aiming to achieve high performance. Its precise measurement capabilities, ease of use, and integration with development tools make it a cornerstone of performance testing in the Java ecosystem. Whether you’re optimizing a small utility function or a complex algorithm, JMH provides the tools and insights needed to ensure your code runs efficiently and meets performance expectations.

4.1 Example Check Using JMH For The Performance Of The Two Expressions

You need to have Maven installed. Let us look at the Java code for the implementation of JMH framework:

package org.javacodegeeks;


import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

public class BenchMark {

    @Benchmark
	@Fork(value = 1, warmups = 2)
	@BenchmarkMode(Mode.Throughput)
	public void init() {
	    // Do nothing
	}

    @Benchmark
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public int op1() {
        int i = 8;

        int op1 =  2 * i * i;
		
		return op1;
    }
	
    @Benchmark
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public int op2() {
        int i = 8;

        int op2 =  2 * (i * i);
		
		return op2;
    }



}

You can compile and execute the above code with the following commands:

 
mvn package
java -jar target/jmh-1.0.0-SNAPSHOT-jar-with-dependencies.jar

The output for the above commands when executed is shown below:

 
bhagvanarch@Bhagvans-MacBook-Air jmh % java -jar target/jmh-1.0.0-SNAPSHOT-jar-with-dependencies.jar
# JMH version: 1.37
# VM version: JDK 17.0.13, OpenJDK 64-Bit Server VM, 17.0.13+0
# VM invoker: /opt/homebrew/Cellar/openjdk@17/17.0.13/libexec/openjdk.jdk/Contents/Home/bin/java
# VM options: 
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.javacodegeeks.BenchMark.init

# Run progress: 0.00% complete, ETA 00:21:40
# Warmup Fork: 1 of 2
# Warmup Iteration   1: 1683919569.733 ops/s
# Warmup Iteration   2: 1777648559.676 ops/s
# Warmup Iteration   3: 1543341995.410 ops/s
# Warmup Iteration   4: 1740437052.017 ops/s
# Warmup Iteration   5: 1752845461.308 ops/s

# Run complete. Total time: 00:21:47

Benchmark        Mode  Cnt           Score           Error  Units
BenchMark.init  thrpt    5  1577067067.141 ± 613762820.801  ops/s
BenchMark.op1    avgt   25           0.618 ±         0.022  ns/op
BenchMark.op2    avgt   25           0.592 ±         0.026  ns/op
bhagvanarch@Bhagvans-MacBook-Air jmh %

As shown in the output, the time taken for the expression 2*i*i is 0.618 ms and for the expression 2 * (i * i) is 0.592 ms. The performance difference is significant.

5. Conclusion

In conclusion, the subtle difference in performance between 2 * (i * i) and 2 * i * i in Java underscores the importance of understanding operator precedence and optimization. While both expressions are mathematically equivalent, the use of parentheses in 2 * (i * i) ensures that the multiplication i * i is evaluated first, which can sometimes aid the compiler in optimizing the calculation more effectively. This clear precedence can reduce computational overhead and improve performance, particularly in high-stakes scenarios where efficiency is paramount.

On the other hand, 2 * i * i relies on the natural operator precedence rules of Java, which perform multiplications from left to right. Although this approach generally has minimal impact on modern compilers’ optimization capabilities, being explicit in your code with parentheses can enhance readability and maintainability. Thus, while the performance gain may be marginal, 2 * (i * i) exemplifies a best practice in coding where clarity can lead to more efficient execution and easier comprehension. By paying attention to such nuances, developers can write more optimized and robust code, contributing to overall better software performance.

6. Download

Download
You can download the full source code of this example here: Why Is 2 * (i * i) Faster Than 2 * i * i in Java?

Bhagvan Kommadi

Bhagvan Kommadi is the Founder of Architect Corner & has around 19 years experience in the industry, ranging from large scale enterprise development to helping incubate software product start-ups. He has done Masters in Industrial Systems Engineering at Georgia Institute of Technology (1997) and Bachelors in Aerospace Engineering from Indian Institute of Technology, Madras (1993). He is member of IFX forum,Oracle JCP and participant in Java Community Process. He founded Quantica Computacao, the first quantum computing startup in India. Markets and Markets have positioned Quantica Computacao in ‘Emerging Companies’ section of Quantum Computing quadrants. Bhagvan has engineered and developed simulators and tools in the area of quantum technology using IBM Q, Microsoft Q# and Google QScript.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button