Thread overview
Lockstep iteration in parallel: Error: cannot have parameter of type `void`
May 19
kdevel
May 20
kdevel
May 20
Sergey
5 days ago
kdevel
May 19
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
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
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
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
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

5 days ago
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.