@snaphat I'm not really advocating SIMD intrinsics in C# or other high-level languages, what I'd like to see is more natural ways to express parallel or vector operations that can then be easily compiled into vector/parallel code without the need for sophisticated reverse-engineering. Again, going back to simply adding two arrays. I should be able to just say
int c = a + b;
where a and b are arrays, and c is an array containing the sum of their respective elements. That is something that can be implemented sequentially, or with SIMD instructions, or that can even run on multiple threads if the arrays are sufficiently large.
There is some of that already in functional constructs like LINQ and functional languages like F#, but needless to say the runtime support for actual vectorization/parallelisation isn't there. So there is work to do on runtimes, but there's also work at the language level. Functional languages, although favoring immutable data and therefore being naturally more amenable to parallelism, were not really designed with this purpose in mind. What if a language was built from the ground up for parallelism?
By switching your language to be inherently parallel you are giving the compiler the task of automatically (auto-magically) mapping parallelism to limited resources or more strictly put: the compiler is responsible for optimizing perfectly parallel code to fit on machine with limited parallel resources (chunking things into tasks and simd instructions). This doesn't necessarily end well because it is actually a fairly difficult problem from a compiler standpoint. Hand-tuned manual parallelism using control-flow languages tends to yield better results.
I wonder if one day optimizers won't beat humans at that game too like they did for assembly code decades ago. In the meantime, you can have declarative/functional constructs and manual parallelism, for instance you insert the AsParallel() extension method where you think the granularity is optimal and everything else stays declarative, so that's already something.
When I say async changes the way programmers think, I mean they can look at a given piece of asynchronous code and reason about it in the same way they do a sequential piece of code: the two look identical with the exception of a few additional keywords, and apart from the asynchrony the logic is the same. Whereas previously you had to build this complicated state machine with callbacks and while the end result was the same, the code looked nothing like the logic you were trying to implement, it was all about handling asynchrony rather than doing what you were actually trying to do. Here's a good example of that: http://mnajder.blogs...ment-using.html
You seem to be making two arguments to me: you want more control in higher level languages but you want less control in lower level languages. Complex programs require complex languages and you aren't going to get a silver bullet of easy-to-use-with-lots-of-power-and-runs-well language. The fact is, no-one has an answer for what the perfect language or runtime is: it's simply an open research topic. Currently, from what I've seen, people think the best alternative to do a layered approach. At the low level you have languages that map well to your architecture and a higher level you have languages that are transformed into lower-level languages. You expose architectural details in the low level languages, but not in the high level languages. On top of that you expose some sort of hinting system at the high level to give help with parallel optimizations (e.g. group these tasks). This is just a vague trend I'm alluding to that has been occurring in HPC.
I pretty much agree with the alternative you describe. I just think that the kind of high level language we use now does not always lend itself well to be transformed into a low-level parallel form, because all these languages were designed at a time where parallelism wasn't a concern; all the "C" languages have the obsolete notion of a "currently executing statement", no notion of vector variables, no parallel constructs except for library extensions, shared global state everywhere, etc. Only Rust and perhaps Go I think are currently attempting to tackle these issues.