Skip to content

Instantly share code, notes, and snippets.

@tombentley
Last active August 29, 2015 14:02
Show Gist options
  • Save tombentley/8a23c3b07f082e89ff6c to your computer and use it in GitHub Desktop.
Save tombentley/8a23c3b07f082e89ff6c to your computer and use it in GitHub Desktop.
This is the code I used to compare the different ways of sorting an Array
// This is the Array constructor I was testing. Note that if we can't create a primitive backing array we don't bother to copy the array again, we use the one we've already sorted.
private static <Element> java.lang.Object createArray(
final TypeDescriptor $reifiedElement,
ceylon.language.Iterable<? extends Element,?> elements,
final Callable<? extends Comparison> comparing) {
int size = Util.toInt(elements.getSize());
java.lang.Class<?> clazz = $reifiedElement.getArrayElementClass();
final java.lang.Object[] sortedArray;
if (comparing != null) {
// If we have a comparing function, fill an Object array and sort it
sortedArray = (java.lang.Object[]) java.lang.reflect.Array
.newInstance($reifiedElement.getArrayElementClass(), size);
final Iterator<?> iterator = elements.iterator();
for (int i=0; i<sortedArray.length; i++) {
sortedArray[i] = iterator.next();
}
Arrays.sort(sortedArray, new ComparingComparator<>(comparing));
elements = new ObjectArray.ObjectArrayIterable<Element>($reifiedElement, (Element[])sortedArray);
} else {
sortedArray = null;
}
final Iterator<?> iterator = elements.iterator();
if (!$reifiedElement.containsNull()) {
if (clazz==String.class) {
//note: we don't unbox strings in an Array<String?>
// because it would break javaObjectArray()
java.lang.String[] array = new java.lang.String[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
String s = (String) iterator.next();
array[i] = s==null ? null : s.value;
}
}
return array;
}
else if (clazz==Integer.class) {
long[] array = new long[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((Integer) iterator.next()).value;
}
}
return array;
}
else if (clazz==Float.class) {
double[] array = new double[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((Float) iterator.next()).value;
}
}
return array;
}
else if (clazz==Character.class) {
int[] array = new int[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((Character) iterator.next()).codePoint;
}
}
return array;
}
else if (clazz==Boolean.class) {
boolean[] array = new boolean[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((Boolean) iterator.next()).booleanValue();
}
}
return array;
}
else if (clazz==java.lang.Boolean.class) {
boolean[] array = new boolean[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Boolean) iterator.next()).booleanValue();
}
}
return array;
}
else if (clazz==java.lang.Character.class) {
char[] array = new char[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Character) iterator.next()).charValue();
}
}
return array;
}
else if (clazz==java.lang.Float.class) {
float[] array = new float[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Float) iterator.next()).floatValue();
}
}
return array;
}
else if (clazz==java.lang.Double.class) {
double[] array = new double[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Double) iterator.next()).doubleValue();
}
}
return array;
}
else if (clazz==java.lang.Byte.class) {
byte[] array = new byte[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Byte) iterator.next()).byteValue();
}
}
return array;
}
else if (clazz==java.lang.Short.class) {
short[] array = new short[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Short) iterator.next()).shortValue();
}
}
return array;
}
else if (clazz==java.lang.Integer.class) {
int[] array = new int[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Integer) iterator.next()).intValue();
}
}
return array;
}
else if (clazz==java.lang.Long.class) {
long[] array = new long[size];
if (elements instanceof Array) {
arraycopy(((Array<?>)elements).array, 0, array, 0, size);
}
else {
for (int i=0; i<array.length; i++) {
array[i] = ((java.lang.Long) iterator.next()).longValue();
}
}
return array;
}
}
if (sortedArray != null) {
return sortedArray;
} else {
java.lang.Object[] array = (java.lang.Object[]) java.lang.reflect.Array
.newInstance($reifiedElement.getArrayElementClass(), size);
for (int i=0; i<array.length; i++) {
array[i] = iterator.next();
}
return array;
}
}
"A random or pseduorandom number generator."
shared interface Random {
"A random number between 0 and the given maximum (inclusive)."
shared formal Integer random(Integer maximum);
}
"A [Linear Congruential Generator](http://en.wikipedia.org/wiki/Linear_congruential_generator) (LCG) pseudorandom number generator,
defined by the recurrence relation:
Xₙ₊₁ ≡ (a Xₙ + x) (mod m)
The period of the generator is *at most* `m`, but **beware you choice of parameters**,
as a poor choice can easily yield a shorter period.
See <http://en.wikipedia.org/wiki/Linear_congruential_generator>
"
shared class LCG(
// Same parameters as java.util.Random, apparently
shared Integer a = 25214903917,
shared Integer c = 11,
shared Integer m = 281474976710656, // 2^48
"The seed"
Integer x0=system.nanoseconds.magnitude)
satisfies Random {
//TODO"`m` must be strictly positive"
//TODOassert(0 < m);
//TODO"`a` must be strictly positive and less than `m`"
//TODOassert(0 < a < m);
//TODO"`c` must be positive and less than `m`"
//TODOassert(0 <= c < m);
//TODO"`x0` must be positive and less than `m`"
//TODOassert(0 <= x0 < m);
variable Integer xn = x0;
shared actual Integer random(Integer maximum) {
//xn = (a * xn + c) % m;
variable value numerator = a * xn + c;
if (numerator < 0) {
// overflow
// TODO What if numerator = Integer.MIN_VALUE, then we overflow again!
numerator = -numerator;
}
xn = numerator % m;
// TODO should be inclusive of the maximum
return xn % maximum;
}
"This LCG equals the given object if `other` is an LCG with
the same values for parameters `a`, `c` and `m`.
In other words if the LCG `other` produces the
same sequence as this one, even if their current states are different."
shared actual Boolean equals(Object other) {
if (is LCG other) {
return a == other.a
&& c == other.c
&& m == other.m;
}
return false;
}
"Returns `a.xor(c).xor(m)` to be compatible with [[equals]]."
shared actual Integer hash => a.xor(c).xor(m);
}
"Returns the given elements randomly permuted using
Durstenfeld's variation of the
[[Fisher-Yates shuffle algorithm|http://en.wikipedia.org/wiki/Fisher–Yates_shuffle]].
Given an unbiased `rng` every permutation will be equally likely."
shared List<Element> shuffle<Element>({Element*} elements, Random rng) {
Array<Element> result = Array(elements);
value n = elements.size;
for (i in n-1..1) {
value j = rng.random(i);
value tmp = result[i];
assert(exists tmp);
assert(exists tmp2 = result[j]);
result.set(i, tmp2);
result.set(j, tmp);
}
return result;
}
class Foo(Integer i) satisfies Comparable<Foo> {
shared actual Comparison compare(Foo other) {
return i <=> other.i;
}
shared actual String string => i.string;
}
void arraySorting() {
void run(Integer n, Integer tries) {
value list = 0..n;
value comparator = function(Integer x, Integer y) => x<=>y;
value shuffled = shuffle(list, LCG());
//print(shuffled);
variable Integer t0;
t0 = system.milliseconds;
for (iteration in 0..tries) {
value array0 = Array<Integer>(shuffled);
array0.sortInPlace(comparator);
if (iteration == 0) {
print(array0.take(10));
}
}
print("array.sortInPlace(comparing): ``tries`` times with a list ``n`` long: ``system.milliseconds-t0``ms");
t0 = system.milliseconds;
for (iteration in 0..tries) {
value array0 = shuffled.sort(comparator);
if (iteration == 0) {
print(array0.take(10));
}
}
print("shuffled.sort(comparing): ``tries`` times with a list ``n`` long: ``system.milliseconds-t0``ms");
t0 = system.milliseconds;
for (iteration in 0..tries) {
value array1 = Array<Integer>(shuffled, comparator);
if (iteration == 0) {
print(array1.take(10));
}
}
print("Array<Integer>(shuffled, comparing): ``tries`` times with a list ``n`` long: ``system.milliseconds-t0``ms");
value mappedShuffled = shuffled.map(Foo);
value mappedComparator = function(Foo x, Foo y) => x<=>y;
t0 = system.milliseconds;
for (iteration in 0..tries) {
value array1 = Array<Foo>(mappedShuffled, mappedComparator);
if (iteration == 0) {
print(array1.take(10));
}
}
print("Array<Foo>(shuffled, comparing): ``tries`` times with a list ``n`` long: ``system.milliseconds-t0``ms");
}
// warmup
print("warmup");
run(100, 100000);
run(1000, 10000);
// the actual run
print("timed");
run(100000, 1000);
}

Most of the benchmark code is stuff to pseudorandomly shuffle a list. We then create a shuffled List<Integer> and time sorting it a number of times. During "warmup" we do lots of sorts over some smallish lists. The "timed" phased does fewer sorts over some longer lists.

Basically we're comparing 26758ms with 23957ms -- about a 10% speed improvement for the initializer, which is real, but hardly compelling.

The Array<Foo>(shuffled, comparing) case is to show that it doesn't much matter whether the Array ends up being instantiated with a primitive backing array or a Foo[]

// This is a sample of the output of the benchmark when run
warmup
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
array.sortInPlace(comparing): 100000 times with a list 100 long: 1085ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
shuffled.sort(comparing): 100000 times with a list 100 long: 992ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Array<Integer>(shuffled, comparing): 100000 times with a list 100 long: 792ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Array<Foo>(shuffled, comparing): 100000 times with a list 100 long: 1152ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
array.sortInPlace(comparing): 10000 times with a list 1000 long: 1645ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
shuffled.sort(comparing): 10000 times with a list 1000 long: 1578ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Array<Integer>(shuffled, comparing): 10000 times with a list 1000 long: 1434ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Array<Foo>(shuffled, comparing): 10000 times with a list 1000 long: 1566ms
timed
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
array.sortInPlace(comparing): 1000 times with a list 100000 long: 26561ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
shuffled.sort(comparing): 1000 times with a list 100000 long: 26758ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Array<Integer>(shuffled, comparing): 1000 times with a list 100000 long: 23957ms
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Array<Foo>(shuffled, comparing): 1000 times with a list 100000 long: 28604ms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment