I know about the Math.sin() and Math.cos() functions, but I'm wondering if there's a way I can create (or use an already-existing) a faster function, given that I don't care about pinpoint accuracy. I'm looking to execute a basic sin or cos calculation, and have it perform essentially as fast as possible. Would simply iterating the sigma a few times be any faster than Math.sin()?
Since you don't care much about accuracy store it in a table that is precomputed or only computed once, this is what I do when I want to avoid calls to Math which can be expensive when done alot.
Roughly
public class CosSineTable {
double[] cos = new double[361];
double[] sin = new double[361];
private static CosSineTable table = new CosSineTable();
private CosSineTable() {
for (int i = 0; i <= 360; i++) {
cos[i] = Math.cos(Math.toRadians(i));
sin[i] = Math.sin(Math.toRadians(i));
}
}
public double getSine(int angle) {
int angleCircle = angle % 360;
return sin[angleCircle];
}
public double getCos(int angle) {
int angleCircle = angle % 360;
return cos[angleCircle];
}
public static CosSineTable getTable() {
return table;
}
}
I leave the optimization of the loop and methods to you.
A pre-calculated table's the way to go. Here's an implementation:
static final int precision = 100; // gradations per degree, adjust to suit
static final int modulus = 360*precision;
static final float[] sin = new float[modulus]; // lookup table
static {
// a static initializer fills the table
// in this implementation, units are in degrees
for (int i = 0; i<sin.length; i++) {
sin[i]=(float)Math.sin((i*Math.PI)/(precision*180));
}
}
// Private function for table lookup
private static float sinLookup(int a) {
return a>=0 ? sin[a%(modulus)] : -sin[-a%(modulus)];
}
// These are your working functions:
public static float sin(float a) {
return sinLookup((int)(a * precision + 0.5f));
}
public static float cos(float a) {
return sinLookup((int)((a+90f) * precision + 0.5f));
}
On my laptop, these were about 6x faster than Math.sin.
I only used one table -- the cost of shifting a cosine into a sine wasn't really discernible.
I used floats, assuming that's what you'll likely use in your calculations, given your preference for performance over precision. It doesn't make much difference here, since the bottleneck is really just the array lookup.
Here are my benchmarks:
public static void main(String[] args) {
int reps = 1<<23;
int sets = 4;
Q.pl(" Trial sinTab cosTab sinLib");
for(int i = 0; i<sets; i++) {
Q.pf("%7d\t%7.2f\t%7.2f\t%7.2f\n", i, testSinTab(reps), testCosTab(reps), testSinLib(reps));
}
}
private static float[] sample(int n) {
Random rand = new Random();
float[] values = new float[n];
for (int i=0; i<n; i++) {
values[i] = 400*(rand.nextFloat()*2-1);
}
return values;
}
private static float testSinTab(int n) {
float[] sample = sample(n);
long time = -System.nanoTime();
for (int i=0; i<n; i++) {
sample[i] = sin(sample[i]);
}
time += System.nanoTime();
return (time/1e6f);
}
private static float testCosTab(int n) {
float[] sample = sample(n);
long time = -System.nanoTime();
for (int i=0; i<n; i++) {
sample[i] = cos(sample[i]);
}
time += System.nanoTime();
return time/1e6f;
}
private static float testSinLib(int n) {
float[] sample = sample(n);
long time = -System.nanoTime();
for (int i=0; i<n; i++) {
sample[i] = (float) Math.sin(sample[i]);
}
time += System.nanoTime();
return time/1e6f;
}
output:
Trial sinTab cosTab sinLib
0 102.51 111.19 596.57
1 93.72 92.20 578.22
2 100.06 107.20 600.68
3 103.65 102.67 629.86
You can try
http://sourceforge.net/projects/jafama/
It uses look-up tables, so it might actually be slower
than Math, especially if the tables are often evicted from CPU cache,
but for thousands of successive calls it can be quite faster.
It also seems slower during class load (maybe the JIT doesn't kicks in then yet),
so you might want to avoid it in that particular use-case.
I know this question is old, but I think it's the fastest java implementation sintable with precision to 65536 elements.
public class MathHelper {
private static double[] a = new double[65536];
public static final double sin(float f) {
return a[(int) (f * 10430.378F) & '\uffff'];
}
public static final double cos(float f) {
return a[(int) (f * 10430.378F + 16384.0F) & '\uffff'];
}
static {
for (int i = 0; i < 65536; ++i) {
a[i] = Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D);
}
}
}
Source: https://github.com/Bukkit/mc-dev/blob/master/net/minecraft/server/MathHelper.java
Related
I am really struggling to figure this out. Essentially I am trying to find what frequency is being played via the mic. To my understand, I need to bruteforce the Goertzel algorithm. So essentially I just try every frequency using the Goertzel algorithm until I find the correct one. However, I do not understand how I actually know when the Goertzel algorithm has found the correct algorithm. Could someone please help me.
MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private Button recordButton;
private TextView result;
private AudioRecord recording;
private static final int RECORDER_SAMPLERATE = 10000;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
double[] dbSample = new double[bufferSize];
short[] sample = new short[bufferSize];
private int frequency = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recordButton = findViewById(R.id.recordButton);
result = findViewById(R.id.resultTextView);
recordButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
recording = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, RECORDER_SAMPLERATE,
RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING, bufferSize);
recording.startRecording();
int bufferReadResult = recording.read(sample, 0, bufferSize);
for (int j = 0; j < bufferSize && j < bufferReadResult; j++) {
dbSample[j] = (double) sample[j];
goertzel.processSample(dbSample[j]);
}
// Is this correct?
magnitude = Math.sqrt(goertzel.getMagnitudeSquared());
if(magnitude > maxMagnitude){
maxMagnitude = magnitude;
System.out.println("Freq is: " + Integer.toString(frequency));
}
goertzel.resetGoertzel();
frequency += 1;
}
});
}
}
Goertzel.java
public class Goertzel {
private float samplingRate;
private float targetFrequency;
private long n;
private double coeff, Q1, Q2;
private double sine, cosine;
public Goertzel(float samplingRate, float targetFrequency, long inN) {
this.samplingRate = samplingRate;
this.targetFrequency = targetFrequency;
n = inN;
}
public void resetGoertzel() {
Q1 = 0;
Q2 = 0;
}
public void initGoertzel() {
int k;
float floatN;
double omega;
floatN = (float) n;
k = (int) (0.5 + ((floatN * targetFrequency) / samplingRate));
omega = (2.0 * Math.PI * k) / floatN;
sine = Math.sin(omega);
cosine = Math.cos(omega);
coeff = 2.0 * cosine;
resetGoertzel();
}
public void processSample(double sample) {
double Q0;
Q0 = coeff * Q1 - Q2 + sample;
Q2 = Q1;
Q1 = Q0;
}
public double[] getRealImag(double[] parts) {
parts[0] = (Q1 - Q2 * cosine);
parts[1] = (Q2 * sine);
return parts;
}
public double getMagnitudeSquared() {
return (Q1 * Q1 + Q2 * Q2 - Q1 * Q2 * coeff);
}
}
You've asked about brute-forcing Goertzel specifically, so here is an annotated JUnit test that illustrates a reasonable approach:
public class TestGoertzel
{
private float[] freqs;
private Goertzel[] goertzels;
private static final int RECORDER_SAMPLERATE = 10000;
private static final int INPUT_SAMPLES = 256; //Roughly 26 ms of audio. This small array size was
//chosen b/c the number of frequency "bins" is typically related to the number of input samples,
//for engineering applications. If we only check 256 samples of audio, our "DFT" need only include
//128 output "bins". You can resize this to suit, but keep in mind that the processing time will
//increase exponentially.
#Test
public void test()
{
freqs = new float[INPUT_SAMPLES / 2]; //To prevent frequency-domain aliasing, we cannot test for 256 frequencies; only the first 128.
goertzels = new Goertzel[freqs.length];
for(int n = 0; n < freqs.length; ++n)
{
freqs[n] = n * RECORDER_SAMPLERATE / INPUT_SAMPLES; //Determine the frequency of a wave that can fit exactly n cycles in a block of audio INPUT_SAMPLES long.
//Create a Goertzel for each frequency "bin":
goertzels[n] = new Goertzel(RECORDER_SAMPLERATE, freqs[n], INPUT_SAMPLES);
goertzels[n].initGoertzel(); //Might as well create them all at the beginning, then "reset" them as necessary.
}
//This gives you an idea of the quality of output that can be had for a real signal from your
//microphone. The curve is not perfect, but shows the "smearing" characteristic of a wave
//whose frequency does not fall neatly into a single "bin":
testFrequency(1500.0f);
//Uncomment this to see a full unit test:
//for(float freq : freqs)
//{
// testFrequency(freq);
//}
}
private void testFrequency(float freqHz)
{
System.out.println(String.format("Testing input signal of frequency %5.1fHz", freqHz));
short[] audio = generateAudioWave(freqHz, (short) 1000);
short[] magnitudes = detectFrequencies(audio);
for(int i = 0; i < magnitudes.length; ++i)
{
System.out.println(String.format("%5.1fHz: %d", freqs[i], magnitudes[i]));
}
}
private short[] generateAudioWave(float freqHz, short peakAmp)
{
short[] ans = new short[INPUT_SAMPLES];
float w0 = (float) ((2 * Math.PI) * freqHz / RECORDER_SAMPLERATE);
for(int i = 0; i < ans.length; ++i)
{
ans[i] = (short) (Math.sin(w0 * i) * peakAmp);
}
return ans;
}
private short[] detectFrequencies(short[] audio)
{
short[] ans = new short[freqs.length];
for(int i = 0; i < goertzels.length; ++i)
{
Goertzel goertzel = goertzels[i];
goertzel.resetGoertzel();
for(short s : audio)
{
goertzel.processSample((double) s);
}
ans[i] = (short) (Math.sqrt(goertzel.getMagnitudeSquared()) * 2 / INPUT_SAMPLES);
}
return ans;
}
}
Basically, for every 256 samples of audio you read in, you take that array, and run it past an array of Goertzels which cover the frequencies you are interested in (each Goertzel only measures one frequency). That gives you an output spectrum. You may interpret that spectrum how you choose; I took your question to mean, "how do you find the frequency of the LOUDEST component of the input audio?". In that case, you would search the return value of detectFrequencies() for the largest magnitude. The corresponding member of freqs is your answer.
The fact is, you probably don't want Goertzel, you want an FFT, due to FFT's superior "computational efficiency". Because Goertzel is somewhat slower (to cover a spectrum as fully as an FFT), you may have trouble getting this answer to run in real time.
As an aside, I don't think a samplerate of 10000 is supported, on Android.
I have written following code for population evolution (Genetic Algorithm Implementation):
Individual.java
import java.util.Random;
public class Individual {
public static int SIZE = 500;
private int[] genes = new int[SIZE];
private double fitnessValue = 0.0;
// Getters and Setters
public void setGene(int index,int gene){
this.genes[index] = gene;
}
public int getGene(int index){
return this.genes[index];
}
public void setFitnessValue(double fitness){
this.fitnessValue = fitness;
}
public double getFitnessValue(){
return this.fitnessValue;
}
//Function to generate a new individual with random set of genes
public void generateIndividual(){
Random rand = new Random();
for(int i=0;i<SIZE;i++){
this.setGene(i, rand.nextInt(2));
}
}
//Mutation Function
public void mutate(){
Random rand = new Random();
int index = rand.nextInt(SIZE);
this.setGene(index, 1-this.getGene(index)); // Flipping value of gene
}
//Function to set Fitness value of an individual
public int evaluate(){
int fitness = 0;
for(int i=0; i<SIZE; ++i) {
fitness += this.getGene(i);
}
this.setFitnessValue(fitness);
return fitness;
}
}
Population.java
import java.util.Random;
public class Population {
final static int ELITISM = 5;
final static int POP_SIZE = 200+ELITISM; //Population size + Elitism (1)
final static int MAX_ITER = 10000;
final static double MUTATION_RATE = 0.05;
final static double CROSSOVER_RATE = 0.7;
public static int generation = 2;
private static Random rand = new Random();
private double totalFitness;
private Individual[] pop;
//Constructor
public Population(){
pop = new Individual[POP_SIZE];
//Initialising population
for(int i=0;i<POP_SIZE;i++){
pop[i] = new Individual();
pop[i].generateIndividual();
}
//Evaluating current population
this.evaluate();
}
//Storing new generation in population
public void setPopulation(Individual[] newPop) {
System.arraycopy(newPop, 0, this.pop, 0, POP_SIZE);
}
//Method to find total fitness of population
public double evaluate(){
this.totalFitness = 0.0;
for (int i = 0; i < POP_SIZE; i++) {
this.totalFitness += pop[i].evaluate();
}
return this.totalFitness;
}
//Getters
public Individual getIndividual(int index) {
return pop[index];
}
//Function to find fittest individual for elitism
public Individual getFittest() {
Individual fittest = pop[0];
for (int i = 0; i < POP_SIZE; i++) {
if (fittest.getFitnessValue() <= getIndividual(i).getFitnessValue()) {
fittest = getIndividual(i);
}
}
return fittest;
}
//CROSSOVER Function : Takes 2 individuals and returns 2 new individuals
public static Individual[] crossover(Individual indiv1,Individual indiv2) {
Individual[] newIndiv = new Individual[2];
newIndiv[0] = new Individual();
newIndiv[1] = new Individual();
int randPoint = rand.nextInt(Individual.SIZE);
int i;
for (i=0; i<randPoint; ++i) {
newIndiv[0].setGene(i, indiv1.getGene(i));
newIndiv[1].setGene(i, indiv2.getGene(i));
}
for (; i<Individual.SIZE; ++i) {
newIndiv[0].setGene(i, indiv2.getGene(i));
newIndiv[1].setGene(i, indiv1.getGene(i));
}
return newIndiv;
}
//Roulette Wheel Selection Function
public Individual rouletteWheelSelection() {
double randNum = rand.nextDouble() * this.totalFitness;
int idx;
for (idx=0; idx<POP_SIZE && randNum>0; idx++) {
randNum -= pop[idx].getFitnessValue();
}
return pop[idx-1];
}
//Main method
public static void main(String[] args) {
Population pop = new Population();
Individual[] newPop = new Individual[POP_SIZE];
Individual[] indiv = new Individual[2];
//Current Population Stats
System.out.print("Generation #1");
System.out.println("Total Fitness = "+pop.totalFitness);
System.out.println("Best Fitness = "+pop.getFittest().getFitnessValue());
int count;
for(int iter=0;iter<MAX_ITER;iter++){
count =0;
//Elitism
newPop[count] = pop.getFittest();
count++;
//Creating new population
while(count < POP_SIZE){
//Selecting parents
indiv[0] = pop.rouletteWheelSelection();
indiv[1] = pop.rouletteWheelSelection();
// Crossover
if (rand.nextDouble() < CROSSOVER_RATE ) {
indiv = crossover(indiv[0], indiv[1]);
}
// Mutation
if ( rand.nextDouble() < MUTATION_RATE ) {
indiv[0].mutate();
}
if ( rand.nextDouble() < MUTATION_RATE ) {
indiv[1].mutate();
}
// add to new population
newPop[count] = indiv[0];
newPop[count+1] = indiv[1];
count += 2;
}
// Saving new population in pop
pop.setPopulation(newPop);
//Evaluating new population
pop.evaluate();
System.out.println("Generation #"+ generation++);
System.out.print("Total Fitness = " + pop.totalFitness);
System.out.println(" ; Best Fitness = " +pop.getFittest().getFitnessValue());
}
Individual bestIndiv = pop.getFittest();
}
}
I have been asked to test my algorithm using following functions:
https://en.wikipedia.org/wiki/Test_functions_for_optimization
Test functions for single objective optimisation
Can anyone explain how it is to be done? Explanation for any one function from the list would be helpful.
What the genes should represent
I'll assume the implementation of your genetic algorithm is correct, as that is beyond the scope of this question.
Right now your fitness function is defined to be the sum of all of the genes:
double fitness = 0;
for(int i=0; i<SIZE; ++i) {
fitness += this.getGene(i);
}
this.setFitnessValue(fitness);
This is a strange thing to do: let's think about an Individual witch will have a high fitness. I hope you see that there is no real optimum, Individuals will simply tend to increase each of their genes because that will archive a higher fitness.
A second problem is that the genes should represent something: what do the doubles in the gene array actually mean? Why do we care? A possible example would be to have them represent the behavior of Individuals in a simulation. That's of course a whole other topic, so we need them to mean something simple so it's easy to calculate their fitness.
Let's let the array have size 1 and let's say x = genes[0]. The Individuals will only have one gene: the x-coordinate. Now we need to define our fitness function, we'll pick Easom with y = 0. This is how I would define the new fitness function:
double fitness = -cos(x)*cos(0)*exp(-(pow(x-PI,2)+pow(0-PI,2)));
With of course the appropriate imports at the top of the class:
import static java.lang.Math.*;
If your program does indeed optimize for fitness it should converge to x = PI. I quickly wrote my own (admittedly very ugly) implementation and it does indeed converge correctly.
One more thing: the genes should be a double[] instead of an int[], because incrementally optimizing a function doesn't really work when x can only be an int.
Why a gene array?
I think your assignment wants you to use an double array as the genes so you end up with a program that can optimize any function with any amount of variables. In programming it is always a good idea to write code that can be reused for multiple different things.
Feel free to ask any questions!
I tried to explain everything as clear as possible, but if you don't understand something feel free to ask!
I've recently started the AI-Class at Coursera and I've a question related to my implementation of the gradient descent algorithm.
Here's my current implementation (I actually just "translated" the mathematical expressions into Java code):
public class GradientDescent {
private static final double TOLERANCE = 1E-11;
private double theta0;
private double theta1;
public double getTheta0() {
return theta0;
}
public double getTheta1() {
return theta1;
}
public GradientDescent(double theta0, double theta1) {
this.theta0 = theta0;
this.theta1 = theta1;
}
public double getHypothesisResult(double x){
return theta0 + theta1*x;
}
private double getResult(double[][] trainingData, boolean enableFactor){
double result = 0;
for (int i = 0; i < trainingData.length; i++) {
result = (getHypothesisResult(trainingData[i][0]) - trainingData[i][1]);
if (enableFactor) result = result*trainingData[i][0];
}
return result;
}
public void train(double learningRate, double[][] trainingData){
int iteration = 0;
double delta0, delta1;
do{
iteration++;
System.out.println("SUBS: " + (learningRate*((double) 1/trainingData.length))*getResult(trainingData, false));
double temp0 = theta0 - learningRate*(((double) 1/trainingData.length)*getResult(trainingData, false));
double temp1 = theta1 - learningRate*(((double) 1/trainingData.length)*getResult(trainingData, true));
delta0 = theta0-temp0; delta1 = theta1-temp1;
theta0 = temp0; theta1 = temp1;
}while((Math.abs(delta0) + Math.abs(delta1)) > TOLERANCE);
System.out.println(iteration);
}
}
The code works quite well but only if I choose an very little alpha, here called learningRate. If it's higher than 0.00001, it diverges.
Do you have any suggestions on how to optimize the implementation, or an explanation for the "Alpha-Issue" and a possible solution for it?
Update:
Here's the main including some sample inputs:
private static final double[][] TDATA = {{200, 20000},{300, 41000},{900, 141000},{800, 41000},{400, 51000},{500, 61500}};
public static void main(String[] args) {
GradientDescent gd = new GradientDescent(0,0);
gd.train(0.00001, TDATA);
System.out.println("THETA0: " + gd.getTheta0() + " - THETA1: " + gd.getTheta1());
System.out.println("PREDICTION: " + gd.getHypothesisResult(300));
}
The mathematical expression of gradient descent is as follows:
To solve this issue, it's necessary to normalize the data with this formular: (Xi-mu)/s.
Xi is the current training set value, mu the average of values in the current column and s the maximum value minus the minimum value of the current column. This formula will get the training data approximately into a range between -1 and 1 which allowes to choose higher learning rates and gradient descent to converge faster.
But it's afterwards necessary to denormalize the predicted result.
private double getResult(double[][] trainingData, boolean enableFactor){
double result = 0;
for (int i = 0; i < trainingData.length; i++) {
result = (getHypothesisResult(trainingData[i][0]) - trainingData[i][1]);
if (enableFactor) result = result*trainingData[i][0];
}
return result;
In this func. result variable overwritten each iteration, the old value being lost. When inputing the values only the last item on array is calculating. Rest of them dont matter.
You should use java.math.BigDecimal for your arithematic operations.
double has its rounding-off issues while performing any arithematic.
I am trying to make as generic as possible method for tweening between various types of values.
So, given a start and end value thats, say, either an Int,Float or Double as well as the number of steps (int), it will return values evenly distributed along those steps in the same type.
However, I am starting to suspect;
a) My knowledge of generics is terrible.
b) This might not be possible :(
So, just to be clear, one example;
SpiffyTween<Double> meep = new SpiffyTween<Double>(1d,10d, 100);
while (meep.hasNext()){
Log.info("value="+meep.next());
}
Would return 0.0,0.1,0.2..etc upto 9.9
But SpiffyTween could also work with other number types without needing separate code for each.
Heres the code I have right now;
class SpiffyTween<T extends Number> implements SpiffyGenericTween<T>
{
static Logger Log = Logger.getLogger("SpiffyTween <Number>");
private T start;
private T end;
int totalsteps=0;
int CurrentStep = 0;
ArrayList<T> steps = new ArrayList<T>();
public SpiffyTween(T start,T end, int steps) {
this.start = start;
this.end = end;
this.totalsteps = steps;
precalculate();
}
private void precalculate() {
//calc step difference
double dif = ((end.doubleValue() -start.doubleValue())/totalsteps);
Log.info("dif="+dif);
int i=0;
while(i<totalsteps){
T stepvalue = (T)((Number)(start.doubleValue() +(dif*i)));
steps.add(stepvalue);
Log.info("add step="+stepvalue);
i++;
}
}
public T next(){
T currentVal = steps.get(CurrentStep);
CurrentStep++;
return currentVal;
}
#Override
public boolean hasNext() {
if (CurrentStep<totalsteps){
return true;
}
return false;
}
}
This works...ish.
While the numbers come out aproximately right occasionally theres values like;
9.600000000000001
or
2.4000000000000004
I am assuming thats to do with the unchecked type conversion here;
T stepvalue = (T)((Number)(start.doubleValue() +(dif*i)));
But I cant work out how to do it better.
Whatever the solution (if theres one), my longterm plan is to try to make similar code that can also work on arrays of various number types. So, you could tween between 3 dimensional points by feeding it an array of the x/y/z co-ordinates of the start and end.
Also, possibly more relevantly, in the code example here its basic addition being done. I probably want other types of tweening possible, so that would make the maths more complex.
Is the better route to convert to, say, BigNumber, and then (somehow) back to the initial T later after all the processing is done?
Thanks in advance for any help or pointers.
YOu don't really need Generics to write code once. Consider the code below. Your exercise is to extend to other dimensions and to ensure caller does not use less than one step:
Tween Class
package com.example.stepup;
public class Tween {
public static int[] get1DimSteps (int start, int end, int steps) {
double[] preciseResult = get1DimSteps((double) start, (double) end, steps);
int[] result = new int[steps];
for (int i=0; i<steps; i++) {
result[i] = (int) (preciseResult[i] + 0.5D);
}
return result;
}
public static double[] get1DimSteps (float start, float end, int steps) {
double[] result = get1DimSteps((double)start, (double)end, steps);
return result;
}
public static double[] get1DimSteps (double start, double end, int steps) {
double distance;
double stepSize;
double[] result = new double[steps];
distance = end - start;
stepSize = distance / steps;
for (int i=0; i < steps; i++) {
result[i] = start + stepSize*i;
}
return result;
}
}
StepupTest Class
package com.example.stepup;
public class StepupTest {
public static void main(String[] args) {
// get steps from "start" to "finish"
int startI = -1;
int endI =999;
float start = (float) startI;
float end = (float) endI;
double startD = (double) startI;
double endD = (double) endI;
int numberOfSteps = 100;
double[] steps = Tween.get1DimSteps( start, end, numberOfSteps);
double[] stepsD = Tween.get1DimSteps(startD, endD, numberOfSteps);
int[] stepsI = Tween.get1DimSteps(startI, endI, numberOfSteps);
for (int i=0; i < numberOfSteps; i++) {
System.out.println(" " + i + ". " + steps[i] + ", " + stepsD[i] + ", " + stepsI[i]);
}
}
}
There are probably hundreds of questions about Java Collections vs. arrays, but this is something I really didn't expect.
I am developing a server for my game, and to communicate between the client and server you need to send packets (obviously), so I did some tests which Collection (or array) I could use best to handle them, HashMap, ArrayList and a PacketHandler array. And the outcome is very unexpected to me, because the ArrayList wins.
The packet handling structure is just like dictionary usage (index to PacketHandler), and because an array is the most primitive form of dictionary use I thought that would easily perform better than an ArrayList. Could someone explain me why this is?
My test
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
public class Main {
/**
* Packet handler interface.
*/
private interface PacketHandler {
void handle();
}
/**
* A dummy packet handler.
*/
private class DummyPacketHandler implements PacketHandler {
#Override
public void handle() { }
}
public Main() {
Random r = new Random();
PacketHandler[] handlers = new PacketHandler[256];
HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();
// packet handler initialization
for (int i = 0; i < 255; i++) {
DummyPacketHandler p = new DummyPacketHandler();
handlers[i] = p;
m.put(new Integer(i), p);
list.add(p);
}
// array
long time = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++)
handlers[r.nextInt(255)].handle();
System.out.println((System.currentTimeMillis() - time));
// hashmap
time = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++)
m.get(new Integer(r.nextInt(255))).handle();
System.out.println((System.currentTimeMillis() - time));
// arraylist
time = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++)
list.get(r.nextInt(255)).handle();
System.out.println((System.currentTimeMillis() - time));
}
public static void main(String[] args) {
new Main();
}
}
I think the problem is quite solved, thanks everybody
The shorter answer is that ArrayList is slightly more optimised the first time, but is still slower in the long run.
How and when the JVM optimise the code before its completely warmed up isn't always obvious and can change between version and based on your command line options.
What is really interesting is what you get when you repeat the test. The reason that makes a difference here is that the code is compiled in stages in the background as you want to have tests where the code is already as fast as it will get right from the start.
There are a few things you can do to make your benchmark more reproducaeable.
generate your random numbers in advance, they are not part of your test but can slow you down.
place each loop in a separate method. The first loop triggers the whole method to be compiled for better or worse.
repeat the test 5 to 10 times and ignore the first one.
Using System.nanoTime() instead of currentTimeMillis() It may not make any difference here but is a good habit to get into.
Use autoboxing where you can so it uses the integer cache or Integer.valueOf(n) which does the same thing. new Integer(n) will always create an object.
make sure your inner loop does something otherwise its quite likely the JIT will optimise it away to nothing. ;)
.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
public class Main {
/**
* Packet handler interface.
*/
private interface PacketHandler {
void handle();
}
/**
* A dummy packet handler.
*/
static class DummyPacketHandler implements PacketHandler {
#Override
public void handle() {
}
}
public static void main(String[] args) {
Random r = new Random();
PacketHandler[] handlers = new PacketHandler[256];
HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();
// packet handler initialization
for (int i = 0; i < 256; i++) {
DummyPacketHandler p = new DummyPacketHandler();
handlers[i] = p;
m.put(new Integer(i), p);
list.add(p);
}
int runs = 10000000;
int[] handlerToUse = new int[runs];
for (int i = 0; i < runs; i++)
handlerToUse[i] = r.nextInt(256);
for (int i = 0; i < 5; i++) {
testArray(handlers, runs, handlerToUse);
testHashMap(m, runs, handlerToUse);
testArrayList(list, runs, handlerToUse);
System.out.println();
}
}
private static void testArray(PacketHandler[] handlers, int runs, int[] handlerToUse) {
// array
long time = System.nanoTime();
for (int i = 0; i < runs; i++)
handlers[handlerToUse[i]].handle();
System.out.print((System.nanoTime() - time)/1e6+" ");
}
private static void testHashMap(HashMap<Integer, PacketHandler> m, int runs, int[] handlerToUse) {
// hashmap
long time = System.nanoTime();
for (int i = 0; i < runs; i++)
m.get(handlerToUse[i]).handle();
System.out.print((System.nanoTime() - time)/1e6+" ");
}
private static void testArrayList(ArrayList<PacketHandler> list, int runs, int[] handlerToUse) {
// arraylist
long time = System.nanoTime();
for (int i = 0; i < runs; i++)
list.get(handlerToUse[i]).handle();
System.out.print((System.nanoTime() - time)/1e6+" ");
}
}
prints for array HashMap ArrayList
24.62537 263.185092 24.19565
28.997305 206.956117 23.437585
19.422327 224.894738 21.191718
14.154433 194.014725 16.927638
13.897081 163.383876 16.678818
After the code warms up, the array is marginally faster.
There are at least a few problems with your benchmark:
you run your tests directly in main, meaning that when you main method gets compiled, the JIT compiler has not had time to optimise all the code because it has not run it yet
the map method creates a new integer each time, which is not fair: use m.get(r.nextInt(255)).handle(); to allow the Integer cache to be used
you need to run your test several times before you can draw conclusions
you are not using the result of what you do in your loops and the JIT is therefore allowed to simply ignore them
monitor GC as it might always run at the same time and bias the results of one of your loop and add a System.gc() call between each loop.
But before doing all that, read this post ;-)
After tweaking your code a bit, I get these results:
Array: 116
Map: 139
List: 117
So array and list are close to identical once compiled and map is slightly slower.
Code:
public class Main {
/**
* Packet handler interface.
*/
private interface PacketHandler {
int handle();
}
/**
* A dummy packet handler.
*/
private class DummyPacketHandler implements PacketHandler {
#Override
public int handle() {
return 123;
}
}
public Main() {
Random r = new Random();
PacketHandler[] handlers = new PacketHandler[256];
HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();
// packet handler initialization
for (int i = 0; i < 255; i++) {
DummyPacketHandler p = new DummyPacketHandler();
handlers[i] = p;
m.put(new Integer(i), p);
list.add(p);
}
long sum = 0;
runArray(handlers, r, 20000);
runMap(m, r, 20000);
runList(list, r, 20000);
// array
long time = System.nanoTime();
sum += runArray(handlers, r, 10000000);
System.out.println("Array: " + (System.nanoTime() - time) / 1000000);
// hashmap
time = System.nanoTime();
sum += runMap(m, r, 10000000);
System.out.println("Map: " + (System.nanoTime() - time) / 1000000);
// arraylist
time = System.nanoTime();
sum += runList(list, r, 10000000);
System.out.println("List: " + (System.nanoTime() - time) / 1000000);
System.out.println(sum);
}
public static void main(String[] args) {
new Main();
}
private long runArray(PacketHandler[] handlers, Random r, int loops) {
long sum = 0;
for (int i = 0; i < loops; i++)
sum += handlers[r.nextInt(255)].handle();
return sum;
}
private long runMap(HashMap<Integer, PacketHandler> m, Random r, int loops) {
long sum = 0;
for (int i = 0; i < loops; i++)
sum += m.get(new Integer(r.nextInt(255))).handle();
return sum;
}
private long runList(List<PacketHandler> list, Random r, int loops) {
long sum = 0;
for (int i = 0; i < loops; i++)
sum += list.get(r.nextInt(255)).handle();
return sum;
}
}