Thread overview
Lockstep iteration in parallel: Error: cannot have parameter of type `void`
May 19, 2023
kdevel
May 19, 2023
Ali Çehreli
May 20, 2023
kdevel
May 20, 2023
Ali Çehreli
May 20, 2023
Sergey
May 23, 2023
kdevel
May 19, 2023
import std.range;
import std.parallelism;

void vec_op (double [] outp, const double [] inp,
   double function (double) f)
{
   foreach (ref a, b; parallel (lockstep (outp, inp)))
      a = f (b);
}

Should this compile? dmd says

[...]/src/phobos/std/parallelism.d(4094): Error: cannot have parameter of type `void`
[...]/src/phobos/std/parallelism.d(4095): Error: cannot have parameter of type `void`
[...]/src/phobos/std/parallelism.d(3619): Error: template instance `std.parallelism.ParallelForeach!(Lockstep!(double[], const(double)[]))` error instantiating
lspar.d(7):        instantiated from here: `parallel!(Lockstep!(double[], const(double)[]))`
May 19, 2023
On 5/19/23 02:17, kdevel wrote:

> Should this compile? dmd says

Multiple points:

- lockstep works only with foreach loops but it's not a range.

- std.range.zip can be used instead but it does not provide 'ref' access to its elements.

- However, since slices are already references to groups of elements, you don't need 'ref' anyway.

- You seem to want to assign to elements in parallel; such functionality already exists in std.parallelism.

- Phobos documentation is not very useful these days as it's not clear from the following page that there are goodies like amap, asyncBuf, etc. It just shows parallel() and a few friends at the top:

  https://dlang.org/phobos/std_parallelism.html

One needs to know to click TaskPool:

  https://dlang.org/phobos/std_parallelism.html#.TaskPool

The following amap example there may be useful for your case but I could not make the types work:

// Same thing, but explicitly allocate an array
// to return the results in.  The element type
// of the array may be either the exact type
// returned by functions or an implicit conversion
// target.
auto squareRoots = new float[numbers.length];
taskPool.amap!sqrt(numbers, squareRoots);

// Multiple functions, explicit output range, and
// explicit work unit size.
auto results = new Tuple!(float, real)[numbers.length];
taskPool.amap!(sqrt, log)(numbers, 100, results);
https://dlang.org/phobos/std_parallelism.html#.TaskPool.amap

Ali

May 20, 2023
Thanks for your explications!

On Friday, 19 May 2023 at 21:18:28 UTC, Ali Çehreli wrote:
> [...]
> - std.range.zip can be used instead but it does not provide 'ref' access to its elements.

How/why does sort [1] work with zipped arrays?

> [...]
>
> The following amap example there may be useful for your case but I could not make the types work:

Do you mean using the function pointer does not work?

> // Same thing, but explicitly allocate an array
> // to return the results in.  The element type
> // of the array may be either the exact type
> // returned by functions or an implicit conversion
> // target.
> auto squareRoots = new float[numbers.length];
> taskPool.amap!sqrt(numbers, squareRoots);

This even seems to work with a static function pointer:

   int main ()
   {
      import std.stdio;
      import std.math;
      import std.parallelism;

      const double [] a = [1., 2., 3., 4.];
      double [] b = [0., 0., 0., 0.];

      writeln (a);
      writeln (b);

      double function (double) fp = &sqrt;
      taskPool.amap!fp (a, b);

      writeln (a);
      writeln (b);
      return 0;
   }

Using an automatic variable gives a deprecation warning

   main.amap!(const(double)[], double[]).amap` function requires a
   dual-context, which is deprecated

[1] https://dlang.org/library/std/range/zip.html
May 20, 2023
On 5/20/23 04:21, kdevel wrote:
> Thanks for your explications!
>
> On Friday, 19 May 2023 at 21:18:28 UTC, Ali Çehreli wrote:
>> [...]
>> - std.range.zip can be used instead but it does not provide 'ref'
>> access to its elements.
>
> How/why does sort [1] work with zipped arrays?

I don't know but wholesale assignment to zip's elements do transfer the effect, which sort does while swapping elements. However, copies of those range elements do not carry that effect:

import std;

void main() {
    auto a = [ 0, 1 ];
    auto z = zip(a);
    z[0] = z[1];
    writeln("worked: ", a);

    zip(a).each!(e => e = 42);
    writeln("  nope: ", a);
}

And each does not take (ref e) as a parameter at least in that  use case.

>> The following amap example there may be useful for your case but I
>> could not make the types work:
>
> Do you mean using the function pointer does not work?

I simply copied the example code bet there were mismatches between float, real, etc.

And I've just discovered something. Which one of the following is the expected documentation?

  https://dlang.org/library/std/parallelism.html

  https://dlang.org/phobos/std_parallelism.html

What paths did I take to get to those? I hope I will soon be motivated enough to fix such quality issues.

Ali

May 20, 2023
On Saturday, 20 May 2023 at 18:27:47 UTC, Ali Çehreli wrote:
> On 5/20/23 04:21, kdevel wrote:
> And I've just discovered something. Which one of the following is the expected documentation?
>
>   https://dlang.org/library/std/parallelism.html
>
>   https://dlang.org/phobos/std_parallelism.html
>
> What paths did I take to get to those? I hope I will soon be motivated enough to fix such quality issues.
>
> Ali

They both. Different versions of documentation generator afaik

May 23, 2023
On Saturday, 20 May 2023 at 18:27:47 UTC, Ali Çehreli wrote:
> [...]
> And I've just discovered something.

Me2! The serial version using array indexing

   void vec_op_naive0 (double [] outp, const double [] inp,
      double function (double) fp)
   {
      enforce (inp.length == outp.length);
      auto i = inp.length;
      while (i--)
         outp [i] = fp (inp [i]);
   }

is nearly thrice as fast as the one using lockstep

   void vec_op (double [] outp, const double [] inp,
      double function (double) fp)
   {
      foreach (ref a, b; lockstep (outp, inp))
         a = fp (b);
   }

I wonder if under this circumstances (lack of speed, lack of parallelism out-of-the-box) it makes any sense to prefer lockstep over
the indexed array access.