A long time ago I wanted to write about the stage at which one of the “ancestors” of the programming languages that are popular today is now. Yes, I'm talking about FORTRAN. I will try to destroy the stereotype that exists in the minds of many developers - that Fortran is an ancient language without any prospects, and certainly no one writes on it anymore. On the contrary, it is very actively evolving and has long been offering a rich set of different functionalities, enshrined in standards, which, by the way, are comparable with the same C / C ++.
From the "old" 77th Fortran, there are not so many left ... in the 95th standard, we could completely create our own data types, dynamically allocate and clear memory, work with pointers, overload functions and operators, and much more. In fact, it differs slightly from its set of tools. However, I do not want to try to compare languages - it is a question of philosophy. I can only say that
Intel's Fortran compiler is very much in demand, and, in fact, is acquired even more actively than the same C ++.
The purpose of this post is to talk about the current state of affairs. Actually, Fortran today is a parallel language, and it became such after the adoption of the Fortran 2008 standard, in which Coarray appeared.
So, about everything in order. The model was based on the SPMD (Single Program MultipleData) programming model. If you are familiar with MPI, then the essence is the same - we are writing our application, copies of which will be executed a certain number of times in parallel. In addition, each copy has its own local data. Those data that need access from different copies are described using a special syntax called Coarray.
For understanding, it is enough to give a simple Hello World example:
program hello write(*,*) "Hello world" end program hello
')
Actually, the most common code. After compiling it with the –coarray key (Intel compiler), we will see “greetings” from several different copies of the program, or, in terms of Coarray, from different images (images). Moreover, their number can be controlled, for example, using the –coarray-num-images = x key, or the environment variable FOR_COARRAY_NUM_IMAGES. It is clear that there is a way to determine in which way the execution takes place. Let's complicate our example:
program hello_image write(*,*) "Hello from image ", this_image(), "out of ", num_images()," total images“ end program hello_image
After launch we will see something like this:
Hello from image 1 out of 4 total images Hello from image 4 out of 4 total images Hello from image 2 out of 4 total images Hello from image 3 out of 4 total images
Obviously, our application was executed 4 times (4 copies / images). Having this data on Coarray, we are, in principle, already capable of creating parallel applications.
That's just very stupid, because there is no answer to the main question - what about the data? To do this, a very simple and clear syntax is introduced:
real, codimension[*] :: x real :: y[*]
Square brackets tell us that we use Coarray.
In this example, these are just scalars, which are still available in every copy of the program. But now we can refer to the value of this scalar in the copy we need (image).
For example, writing y [2], we turn to the value of y in image 2. This opens up opportunities for us to “present” parallel work with data.
Naturally, there are a number of logical restrictions imposed on Coarray'i, such as, for example, any attempt to associate a Coarray object with another object through pointers, or transferring Coarray objects to C code.
Let's look at a few more examples, assuming that we have already declared the variable x as Coarray earlier:
x = 42.0
In this case, we operate on the local for the image of the variable x.
As soon as square brackets appear in our code, this is an explicit pointer to the fact that the variable is being accessed in another program image:
x[3] = 42.0 ! 42 3 x = x[1] ! 1 x[i] = x[j] ! I j
What is good about Coarray is that, unlike pure MPI, we don’t care about sending or receiving messages. All this on the shoulders of the implementation (which already uses the same MPI). But we are "over" this. In addition, the code will work both on systems with distributed memory and on systems with shared memory. Just change the key to coarray = shared or coarray = distributed.
Since we have data in different copies of our program, it is logical to assume that there must be means to synchronize them. Of course they are. This, for example, the construction of SYNC ALL, synchronizes all images. There is also SYNC IMAGES (), which allows you to synchronize only certain images.
One more example:
integer, codimension[*] :: fact integer :: i, factorial fact = this_image() SYNC ALL if ( this_image() == 1 ) then factorial = 1 do i = 1, num_images() factorial = factorial * fact[i] end do write num_images(), 'factorial is ', factorial end if
Naturally, it is not the fastest way to calculate factorial, but it is a good illustration of the essence of working with Coarray.
To begin with, declare fact as Coarray, and then in each image we assign a value equal to the image number. Before we multiply all the values, you need to make sure that they are already assigned, so we use SYNC ALL. And in the image with the number 1, this supposedly "master image", we calculate the factorial.
As a result, we got a very effective tool - a part of the language that allows you to create parallel applications for systems with different memory organization. Naturally, in the compiler support and implementation of Coarray, the main difficulty is performance. At the moment, it still remains not the strongest side ... but here there are great prospects for various compilers.
I’m finishing my brief description regarding the “new buns” from the last accepted standard. I hope it was not very boring to watch the Fortran code. If the reviews show the opposite and wake up a lively interest on this topic, I will not deny you the pleasure and continue the topic. And now thank you all for your attention.