Tell me more ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I just noticed that every modern OO programming language that I am at least somewhat familiar with (which is basically just Java, C# and D) allows covariant arrays. That is, a string array is an object array:

Object[] arr = new String[2];   // Java, C# and D allow this

Covariant arrays are a hole in the static type system. They make type errors possible that cannot be detected at compile-time, so every write to an array must be checked at runtime:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

This seems like a terrible performance hit if I do lots of array stores. C++ does not have covariant arrays, so there is no need to do such a runtime check, which means there is no performance penalty.

Is there any analysis done to reduce the number of runtime checks necessary? For example, if I say:

arr[1] = arr[0];

one could argue that the store cannot possibly fail. I'm sure there are lots of other possible optimizations I haven't thought of. Now, do modern compilers actually do these kinds of optimizations, or do I have to live with the fact that, for example, a Quicksort always does O(n log n) unnecessary runtime checks?

Are there other modern OO languages (besides the ones mentioned) that do not have covariant arrays?

share|improve this question
7  
I'm confused why you are suggesting C++ is faster than other languages at a feature C++ doesn't even support. – eco Jan 17 '12 at 22:03
14  
@eco: C++ is faster with array access because it doesn't support co-variant arrays. Fred wants to know if it's possible for "modern OO languages" to elide the overhead of co-variant arrays to get closer to C++ speeds. – Mooing Duck Jan 17 '12 at 22:08
11  
"code that compiles but throws exceptions at run-time is bad news" – Jesse Jan 17 '12 at 22:11
3  
@Jesse And millions of people write reliable, highly scalable code in dynamic languages. Imo: If you don't write test cases for your code I don't care whether there are compiler errors or not, I'm not going to trust it anyhow. – Voo Jan 17 '12 at 22:27
5  
@Jesse And when else would you expect exceptions but at runtime? The problem isn't code which throws exceptions at runtime - there are plenty of cases where that makes good sense - the problem is code which is guaranteed to be wrong which doesn't get statically caught by the compiler but instead results in an exception at runtime. – Jonathan M Davis Jan 17 '12 at 23:00
show 13 more comments

migrated from stackoverflow.com Jan 17 '12 at 22:45

6 Answers

up vote 26 down vote accepted

D doesn't have covariant arrays. It allowed them prior to the most recent release (dmd 2.057), but that bug has been fixed.

An array in D is effectively just a struct with a pointer and a length:

struct A(T)
{
    T* ptr;
    size_t length;
}

Bounds checking is done normally when indexing an array, but it's removed when you compile with -release. So, in release mode, there's no real performance difference between arrays in C/C++ and those in D.

share|improve this answer
1  
That is so awesome! – FredOverflow Jan 17 '12 at 22:25
2  
@BenVoigt Then the online docs need to be updated. They aren't always 100% up-to-date unfortunately. The conversion will work with const U[], since then you can't assign the wrong type to the array's elements, but T[] definitely does not convert to U[] as long as U[] is mutable. Allowing covariant arrays like it did before was a serious design flaw which has now been corrected. – Jonathan M Davis Jan 17 '12 at 23:57
3  
I've now create a bug report for the array docs, so you can track it's progress there if you'd like. – Jonathan M Davis Jan 18 '12 at 0:03
3  
@JonathanMDavis: This is probably the best and worst thing about D at the same time. It's not a standard, so the language rules can be fixed when a problem like this is found. But since it's not a standard, and potentially-breaking changes are made, that will scare away a lot of users. – Ben Voigt Jan 18 '12 at 1:23
1  
@BenVoigt Breaking changes to the language are becoming much rarer, but in this particular case, any code which would have been broken was buggy anyway, so it's actually helping. – Jonathan M Davis Jan 18 '12 at 2:25
show 7 more comments

Yes, one crucial optimization is this:

sealed class Foo
{
}

In C#, this class can't be a supertype for any type, so you can avoid the check for an array of type Foo.

And to the second question, in F# co-variant arrays are not allowed (but I guess the check will remain in the CLR unless it's found unnecessary in optimizations at runtime)

let a = [| "st" |]
let b : System.Object[] = a // Fails

Array covariance in F#

A somewhat related problem is array bound-checking. This might be an interesting (but old) read about optimizations done in the CLR (covariance is also mentioned 1 place): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the-clr.aspx

share|improve this answer
2  
Scala also prevents this construct: val a = Array("st"); val b: Array[Any] = a is illegal. (However, arrays in Scala are ... special magic ... due to the underlying JVM used.) – pst Jan 17 '12 at 22:00
@pst How about making that comment into a full-fledged answer? :) – FredOverflow Jan 17 '12 at 22:11
1  
@FredOverflow Because it's not :) Just noting that Scala and F# are similar in this aspect (and different from Java and C#). – pst Jan 17 '12 at 22:13

Java answer:

I take it you haven't actually benchmarked the code, have you? In general 90% of all dynamic casts in Java are free because the JIT can elide them (quicksort should be a good example for this) and the rest are one ld/cmp/brsequence which is absolutely predictable (if not, well why the hell is your code throwing all those dynamic cast exceptions?).

We do the load much earlier than the actual compare, the branch is correctly predicted in 99.9999% (made up statistic!) of all cases so we don't stall the pipeline (assuming we don't hit the memory with the load, if not well that will be noticeable, but then the load is necessary anyhow). Hence the cost is 1 clock cycle IF the JIT cannot avoid the check at all.

Some overhead? Sure, but I doubt you'll ever notice it..

Edit: Source, some coarse knowledge about hotspot, but much more important: I remember Cliff mentioning that sometime ago - I'll look for the post later if I don't forget.

share|improve this answer
If you can get Cliff's link it would be great, his talks are awesome :) – Matthieu M. Jan 18 '12 at 10:25
@MatthieuM. I talked with a colleague about it and he also remembered it. Not a talk, but a blogpost, either way a great read: here. Search for "dynamic casts" for the interesting part here. – Voo Jan 18 '12 at 10:43
Thanks! Really interesting though Cliff seems a bit partial to Java (as expected). – Matthieu M. Jan 18 '12 at 12:10
@Matthieu One certainly has to bear Cliff's history in mind (and his current job) and one can address things from a different mindset (although it does seem pretty reasonable to me and he points out some of java's obvious weaknesses). For something like "overhead of dynamic casts" Cliff's probably the foremost expert at least for Hotspot derivatives though and it's a nice objective topic. – Voo Jan 18 '12 at 15:10
He's definitely a Java expert, and I seriously doubt that my C++ programs can compete with his Java ones :p His optimizations for the Azul hardware and the massively parallel Java code he worked on have definitely opened my mind! – Matthieu M. Jan 18 '12 at 15:48

D does not allow covariant arrays.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

As you say, it would be a hole in the type system to allow this.

You can be forgiven for the mistake, as this bug was only just fixed in the lastest DMD, released on December 13th.

Array access in D is just as fast as in C or C++.

share|improve this answer
According to d-programming-language.org/arrays.html "A static array T[dim] can be implicitly converted to one of the following: ... U[] ... A dynamic array T[] can be implicitly converted to one of the following: U[] ... where U is a base class of T." – Ben Voigt Jan 17 '12 at 23:33
1  
@BenVoigt: Out of date docs. – BCS Jan 18 '12 at 0:28
1  
@BenVoigt: I have created a pull request to update the documentation. Hopefully this will be resolved soon. Thanks for pointing it out. – Peter Alexander Jan 19 '12 at 12:52

From test I have done on a cheap laptop, the difference between using int[] and Integer[] is about 1.0 ns. The difference is likely to be due to the extra check for the type.

Generally Objects are only used for higher level logic when not every ns counts. If you need to save every ns, I would avoid using higher level constructs like Objects. Assignments alone are likely to be very small factor in any real program. e.g. creating a new Object on the same machine is 5 ns.

Calls to compareTo are likely to be much more expensive, esp if you using a complex object like String.

share|improve this answer

You asked about other modern OO languages? Well, Delphi avoids this problem entirely by having string be a primitive, not an object. So an array of strings is exactly an array of strings and nothing else, and any operations on them are as fast as native code can be, with no type checking overhead.

However, string arrays are not used very often; Delphi programmers tend to favor the TStringList class. For a minimal amount of overhead it provides a set of string-group methods that are useful in so many situations that the class has been compared to a Swiss Army Knife. So it's idiomatic to use a string list object instead of a string array.

As for other objects in general, the problem does not exist because in Delphi, like in C++, arrays are not covariant, in order to prevent the kind of type system holes described here.

share|improve this answer
5  
Strings were not an important part of the question. Substitute any other class type. – Ben Voigt Jan 17 '12 at 23:20
@ben: For other object types, the problem does not exist either, because object arrays are not covariant. – Mason Wheeler Jan 17 '12 at 23:23
5  
Then you should make that your answer, instead of some digression about TStringList. – Ben Voigt Jan 17 '12 at 23:25
@ben: OK, you're probably right. Edited to include this information. – Mason Wheeler Jan 18 '12 at 0:22

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.