Helpful D Techniques Ali ehreli November 21 The speaker With D - - PowerPoint PPT Presentation

helpful d techniques
SMART_READER_LITE
LIVE PREVIEW

Helpful D Techniques Ali ehreli November 21 The speaker With D - - PowerPoint PPT Presentation

1 / 121 Helpful D Techniques Ali ehreli November 21 The speaker With D since 2009 Love at first sight: Created a T urkish D site 1 , translated 2 article to Andrei Alexandrescu's "The Case for D" T urkish 3 1.


slide-1
SLIDE 1

Helpful D Techniques

Ali Çehreli

November 21

1 / 121
slide-2
SLIDE 2

The speaker

With D since 2009

  • Love at first sight: Created a T

urkish D site

1, translated

Andrei Alexandrescu's "The Case for D"

2 article to

T urkish

3

1. http://ddili.org 2. https://www.drdobbs.com/parallel/the-case-for-d/217801225 3. http://ddili.org/makale/neden_d.html

2 / 121

slide-3
SLIDE 3

The speaker

With D since 2009

  • Love at first sight: Created a T

urkish D site

1, translated

Andrei Alexandrescu's "The Case for D"

2 article to

T urkish

3

  • Known for the free book "Programming in D"

4

∘ "A happy accident"

5

∘ Recently available on Educative.io as an interactive course: ∙ First part

6

∙ Second part

7

1. http://ddili.org 2. https://www.drdobbs.com/parallel/the-case-for-d/217801225 3. http://ddili.org/makale/neden_d.html 4. http://ddili.org/ders/d.en/index.html 5. https://dlang.org/blog/2016/06/29/programming-in-d-a-happy-accident/ 6. https://www.educative.io/courses/programming-in-d-ultimate-guide 7. https://www.educative.io/collection/10370001/5620751206973440

3 / 121

slide-4
SLIDE 4

The speaker (continued)

Currently at Mercedes-Benz Research and Development, North America

  • Using D for ROS Bag File Manipulation for Autonomous

Driving

1

1. https://dconf.org/2019/talks/cehreli.html

4 / 121

slide-5
SLIDE 5

The speaker (continued)

Currently at Mercedes-Benz Research and Development, North America

  • Using D for ROS Bag File Manipulation for Autonomous

Driving

1

  • A project by Daimler and Bosch, a "happy place"

1. https://dconf.org/2019/talks/cehreli.html

5 / 121

slide-6
SLIDE 6

Contents

  • Introduction
  • Engineering with D
  • Mini experience report since DConf 2019
  • Various productive features of D

∘ Parallelism ∘ Concurrency ∘ More ...

6 / 121

slide-7
SLIDE 7

Clicks, not slides ▼ ▼ ▼

Contents

  • Introduction
  • Engineering with D
  • Mini experience report since DConf 2019
  • Various productive features of D

∘ Parallelism ∘ Concurrency ∘ More ...

7 / 121

slide-8
SLIDE 8

Engineering with D

  • Involves few bugs
  • Is very productive
  • Is a lot of fun

8 / 121

slide-9
SLIDE 9

Engineering with D

  • Involves few bugs
  • Is very productive
  • Is a lot of fun

Subjectively, D makes a better engineer:

  • Less perfectionist

9 / 121

slide-10
SLIDE 10

Engineering with D

  • Involves few bugs
  • Is very productive
  • Is a lot of fun

Subjectively, D makes a better engineer:

  • Less perfectionist
  • More pragmatic

10 / 121

slide-11
SLIDE 11

Engineering with D

  • Involves few bugs
  • Is very productive
  • Is a lot of fun

Subjectively, D makes a better engineer:

  • Less perfectionist
  • More pragmatic
  • Acknowledges organic growth (e.g. @nogc vs. pure is just fine)

11 / 121

slide-12
SLIDE 12

Engineering with D

  • Involves few bugs
  • Is very productive
  • Is a lot of fun

Subjectively, D makes a better engineer:

  • Less perfectionist
  • More pragmatic
  • Acknowledges organic growth (e.g. @nogc vs. pure is just fine)
  • Can afford to be less principled because

∘ D is both a prototype language and a production language ∘ D provides plasticity

See: Presentations by Liran Zvibel

1 and Laeeth Isharc 2

1. http://dconf.org/2018/talks/zvibel.html 2. http://dconf.org/2019/talks/isharc.html

12 / 121

slide-13
SLIDE 13

unittest pragmatism

One of the most useful features of D, ingrained in D coding:

int halved(int value) { return value / 2; } unittest { assert(42.halved == 21); }

Note: Thanks to UFCS (universal function call syntax) 42.halved is the same as halved(42). 13 / 121

slide-14
SLIDE 14

unittest pragmatism

One of the most useful features of D, ingrained in D coding:

int halved(int value) { return value / 2; } unittest { assert(42.halved == 21); }

Note: Thanks to UFCS (universal function call syntax) 42.halved is the same as halved(42).

D's unittest blocks are as underpowered as it gets:

  • No test suites, fixtures, mocks, fakes, etc.
  • Nothing but assert (and assertThrown and friends)

14 / 121

slide-15
SLIDE 15

unittest pragmatism

One of the most useful features of D, ingrained in D coding:

int halved(int value) { return value / 2; } unittest { assert(42.halved == 21); }

Note: Thanks to UFCS (universal function call syntax) 42.halved is the same as halved(42).

D's unittest blocks are as underpowered as it gets:

  • No test suites, fixtures, mocks, fakes, etc.
  • Nothing but assert (and assertThrown and friends)

It would be a huge loss if unittest disappeared.

15 / 121

slide-16
SLIDE 16

static if pragmatism

A very useful feature that has been needed

1 for years:

struct S(T) { static if (isIntegral!T) { int i; // Injected member // (good kind of magic, almost cheating) // ... } else { // ... } }

1. https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design

16 / 121

slide-17
SLIDE 17

static if pragmatism

A very useful feature that has been needed

1 for years:

struct S(T) { static if (isIntegral!T) { int i; // Injected member // (good kind of magic, almost cheating) // ... } else { // ... } }

"Considered"

2 to be "harder to read and understand", "provides ample

  • pportunities for confution[sic] and mistakes", etc.

1. https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design 2. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3613.pdf

17 / 121

slide-18
SLIDE 18

static if pragmatism

A very useful feature that has been needed

1 for years:

struct S(T) { static if (isIntegral!T) { int i; // Injected member // (good kind of magic, almost cheating) // ... } else { // ... } }

"Considered"

2 to be "harder to read and understand", "provides ample

  • pportunities for confution[sic] and mistakes", etc.

It would be a huge loss if static if disappeared.

1. https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design 2. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3613.pdf

18 / 121

slide-19
SLIDE 19

Range pragmatism

Ranges are very useful and ubiquitous in modern D code:

  • Lazy evaluations
  • Minimal memory usage
  • Component programming
  • Pipeline programming
  • Reduced loops
  • etc.

/* A range algorithm that generates 0, 1, 2, 3, and 4 | | A range algorithm that applies 'foo(i)' to each of those | | ▼ ▼ */ ▁▁▁▁▁▁ ▁▁▁▁▁▁▁ 5.iota.map!foo // ← A range

19 / 121

slide-20
SLIDE 20

Range pragmatism (continued)

There are issues:

  • Some algorithms like find() naturally return one thing

not a range of elements.

20 / 121

slide-21
SLIDE 21

Range pragmatism (continued)

There are issues:

  • Some algorithms like find() naturally return one thing

not a range of elements.

  • If some algorithms like partition() returned an

iterator, the programmers could make a range from the left-hand side or the right-hand side. (See std.algorithm.partition3.)

21 / 121

slide-22
SLIDE 22

Range pragmatism (continued)

There are issues:

  • Some algorithms like find() naturally return one thing

not a range of elements.

  • If some algorithms like partition() returned an

iterator, the programmers could make a range from the left-hand side or the right-hand side. (See std.algorithm.partition3.) It would be a huge loss if ranges disappeared.

22 / 121

slide-23
SLIDE 23

Statistics

D code at Mercedes-Benz Research and Development, North America:

Structure 2019 2020 programs 3 7 files 25 50 lines 4600 12000

23 / 121

slide-24
SLIDE 24

Statistics

D code at Mercedes-Benz Research and Development, North America:

Structure 2019 2020 programs 3 7 files 25 50 lines 4600 12000

1 of every 25 lines contains a format string literal.

Function 2019 2020 format 182 381 writefln 54 134 writef 5 13

24 / 121

slide-25
SLIDE 25

Format string literals

Should be considered to be a "bug":

format("Hello, %s. Today is %s.", name, day)

25 / 121

slide-26
SLIDE 26

Format string literals

Should be considered to be a "bug":

format("Hello, %s. Today is %s.", name, day)

Indispensably useful but "not good enough":

format!"Hello, %s. Today is %s."(name, day)

26 / 121

slide-27
SLIDE 27

Format string literals

Should be considered to be a "bug":

format("Hello, %s. Today is %s.", name, day)

Indispensably useful but "not good enough":

format!"Hello, %s. Today is %s."(name, day)

Enter DIP 1036 -- Formatted String Tuple Literals

1:

Thank you, Adam D. Ruppe and Steven Schveighoffer. (Based on past work by Walter Bright, Jason Helson, Jonathon Marler, and others.)

format!(i"Hello, $name. Today is $day.")

1. https://github.com/dlang/DIPs/blob/master/DIPs/DIP1036.md

27 / 121

slide-28
SLIDE 28

Garbage collector statistics

Run your program with a special command line option:

$ my_program "--DRT-gcopt=profile:1" <arguments to my_program> See: Garbage Collection specification

1 for other D runtime command line options.

1. https://dlang.org/spec/garbage.html

28 / 121

slide-29
SLIDE 29

Garbage collector statistics

Run your program with a special command line option:

$ my_program "--DRT-gcopt=profile:1" <arguments to my_program> See: Garbage Collection specification

1 for other D runtime command line options.

Program real time user time GC memory GC count GC time GC pause total GC pause max prg1 using 60G file 3m40s 15m35s 4G 220 3.7s 3.4s 54ms prg1 using 0.3G file 0m2s 0m6s 0.8G 12 0.2s 0.2s 50ms prg2 using 2 x 60G files 1m7s 0m46s 19G 64 0.3s 0.2s 13ms prg2 using 2 x 0.044G files 0m1s 0m1s 6G 6 0.016s 0.015s 4ms

1. https://dlang.org/spec/garbage.html

29 / 121

slide-30
SLIDE 30

Profiling memory allocations

Compile your program with the --profile=gc switch:

$ dmd --profile=gc my_program.d

30 / 121

slide-31
SLIDE 31

Profiling memory allocations

Compile your program with the --profile=gc switch:

$ dmd --profile=gc my_program.d $ ./my_program $ cat profilegc.log bytes allocated, allocations, type, function, file:line 704 4 core.thread.osthread.Thread std.concurrency._spawn!()void [...] 704 4 int[] my_program.main.__lambda1 my_program.d:23 704 4 std.concurrency.MessageBox std.concurrency._spawn!()void [...] 384 4 std.concurrency.LinkTerminated std.concurrency.MessageBox [...] 256 4 closure std.concurrency._spawn!()void function()int, shared [...] 16 1 closure D main my_program.d:19

31 / 121

slide-32
SLIDE 32

Reducing memory allocations

Remove premature pessimization:

int[] outer; while (a) { int[] inner; while (b) { inner ~= e; // Line 8 }

  • uter ~= bar(inner);

// Line 11 }

32 / 121

slide-33
SLIDE 33

Reducing memory allocations

Remove premature pessimization:

int[] outer; while (a) { int[] inner; while (b) { inner ~= e; // Line 8 }

  • uter ~= bar(inner);

// Line 11 }

bytes allocated, allocations, type, function, file:line 18000000 259000 int[] deneme.foo deneme.d:8 11040000 15000 int[] deneme.foo deneme.d:11

33 / 121

slide-34
SLIDE 34

Reducing memory allocations (continued)

Reuse the same array for all loop iterations:

int[] outer; int[] inner; while (a) { inner.length = 0; // Treat as empty inner.assumeSafeAppend; // Reuse existing memory // (DON'T DO THOSE. FOR DEMONSTRATION PURPOSES ONLY.) while (b) { inner ~= e; // Line 10 }

  • uter ~= bar(inner);

// Line 13 }

34 / 121

slide-35
SLIDE 35

Reducing memory allocations (continued)

Reuse the same array for all loop iterations:

int[] outer; int[] inner; while (a) { inner.length = 0; // Treat as empty inner.assumeSafeAppend; // Reuse existing memory // (DON'T DO THOSE. FOR DEMONSTRATION PURPOSES ONLY.) while (b) { inner ~= e; // Line 10 }

  • uter ~= bar(inner);

// Line 13 }

bytes allocated, allocations, type, function, file:line 11040000 15000 int[] deneme.foo deneme.d:13 816000 ← was 18M 8000 int[] deneme.foo deneme.d:10

35 / 121

slide-36
SLIDE 36

Reducing memory allocations (continued)

Use static Appender:

// Remember: These are thread-local static Appender!(int[]) outer; static Appender!(int[]) inner;

  • uter.clear();

// Clear state from last call while (a) { inner.clear(); // Clear state from last iteration while (b) { inner ~= e; }

  • uter ~= bar(inner.data);

} Warning: : Thread-safe but non-reentrant.

36 / 121

slide-37
SLIDE 37

Reducing memory allocations (continued)

Use static Appender:

// Remember: These are thread-local static Appender!(int[]) outer; static Appender!(int[]) inner;

  • uter.clear();

// Clear state from last call while (a) { inner.clear(); // Clear state from last iteration while (b) { inner ~= e; }

  • uter ~= bar(inner.data);

} Warning: : Thread-safe but non-reentrant.

bytes allocated, allocations, type, function, file:line 64 2 std.array.Appender!(int[]) [...]

37 / 121

slide-38
SLIDE 38

Various Productive D Features

38 / 121

slide-39
SLIDE 39

Range format specifiers

(Also known as compound format specifier and grouping format specifier.) 5.iota.writefln!"%(%s%)"; // prints 01234 ▔▔▔▔▔▔

39 / 121

slide-40
SLIDE 40

Range format specifiers

(Also known as compound format specifier and grouping format specifier.) 5.iota.writefln!"%(%s%)"; // prints 01234 ▔▔▔▔▔▔

  • %( Opening specifier

40 / 121

slide-41
SLIDE 41

Range format specifiers

(Also known as compound format specifier and grouping format specifier.) 5.iota.writefln!"%(%s%)"; // prints 01234 ▔▔▔▔▔▔

  • %( Opening specifier
  • %) Closing specifier

41 / 121

slide-42
SLIDE 42

Range format specifiers

(Also known as compound format specifier and grouping format specifier.) 5.iota.writefln!"%(%s%)"; // prints 01234 ▔▔▔▔▔▔

  • %( Opening specifier
  • %) Closing specifier
  • Anything in between is per element (e.g. %s above)

42 / 121

slide-43
SLIDE 43

Range format specifiers

(Also known as compound format specifier and grouping format specifier.) 5.iota.writefln!"%(%s%)"; // prints 01234 ▔▔▔▔▔▔

  • %( Opening specifier
  • %) Closing specifier
  • Anything in between is per element (e.g. %s above)

Anything "after the element specifier" is element separator:

5.iota.writefln!"%(%s, %)"; // 0, 1, 2, 3, 4 ▔▔ ▔▔ good: not printed here

43 / 121

slide-44
SLIDE 44

Range format specifiers (continued)

T

  • o much can be missing:

5.iota.writefln!"%(<%s>\n%)"; ▔ ▔▔▔

44 / 121

slide-45
SLIDE 45

Range format specifiers (continued)

T

  • o much can be missing:

5.iota.writefln!"%(<%s>\n%)"; ▔ ▔▔▔

<0> <1> <2> <3> <4 '>' is not printed

45 / 121

slide-46
SLIDE 46

Range format specifiers (continued)

T

  • o much can be missing:

5.iota.writefln!"%(<%s>\n%)"; ▔ ▔▔▔

<0> <1> <2> <3> <4 '>' is not printed

%| specifies where the actual separator starts:

5.iota.writefln!"%(<%s>%|\n%)"; ▔▔

46 / 121

slide-47
SLIDE 47

Range format specifiers (continued)

T

  • o much can be missing:

5.iota.writefln!"%(<%s>\n%)"; ▔ ▔▔▔

<0> <1> <2> <3> <4 '>' is not printed

%| specifies where the actual separator starts:

5.iota.writefln!"%(<%s>%|\n%)"; ▔▔

<0> <1> <2> <3> <4> '>' is now a part of all elements

47 / 121

slide-48
SLIDE 48

Range format specifiers (continued)

Strings are double-quoted (and characters are single- quoted) by default:

["monday", "tuesday"].writefln!"%(%s, %)"; // "monday", "tuesday"

48 / 121

slide-49
SLIDE 49

Range format specifiers (continued)

Strings are double-quoted (and characters are single- quoted) by default:

["monday", "tuesday"].writefln!"%(%s, %)"; // "monday", "tuesday"

If not desired, open with %-(:

["monday", "tuesday"].writefln!"%-(%s, %)"; // monday, tuesday ▔▔▔

49 / 121

slide-50
SLIDE 50

Range format specifiers (continued)

Can be nested:

5.iota.map!(i => i.iota).writefln!"%(%(%s, %)\n%)"; ▔▔▔▔ ▔▔ ▔▔

50 / 121

slide-51
SLIDE 51

Range format specifiers (continued)

Can be nested:

5.iota.map!(i => i.iota).writefln!"%(%(%s, %)\n%)"; ▔▔▔▔ ▔▔ ▔▔

← (The range for outer 0 is empty.) 0, 1 0, 1, 2 0, 1, 2, 3

51 / 121

slide-52
SLIDE 52

Range format specifiers (continued)

Can be nested:

5.iota.map!(i => i.iota).writefln!"%(%(%s, %)\n%)"; ▔▔▔▔ ▔▔ ▔▔

← (The range for outer 0 is empty.) 0, 1 0, 1, 2 0, 1, 2, 3

For associative arrays, the first specifier is for the key and the second specifier is for the value.

auto aa = [ "a" : "one", "b" : "two" ]; aa.writefln!"%-(%s is %s\n%)"; ▔▔ ▔▔

b is two a is one

52 / 121

slide-53
SLIDE 53

Decimal place separator

%, is for decimal place separator:

  • 3 decimal places by default
  • Comma by default

writefln!"%,s"(123456789); // 123,456,789 ▔▔

53 / 121

slide-54
SLIDE 54

Decimal place separator

%, is for decimal place separator:

  • 3 decimal places by default
  • Comma by default

writefln!"%,s"(123456789); // 123,456,789 ▔▔ writefln!"%,*s"(6, 123456789); // 123,456789

54 / 121

slide-55
SLIDE 55

Decimal place separator

%, is for decimal place separator:

  • 3 decimal places by default
  • Comma by default

writefln!"%,s"(123456789); // 123,456,789 ▔▔ writefln!"%,*s"(6, 123456789); // 123,456789 writefln!"%,?s"('·', 123456789); // 123·456·789

55 / 121

slide-56
SLIDE 56

Decimal place separator

%, is for decimal place separator:

  • 3 decimal places by default
  • Comma by default

writefln!"%,s"(123456789); // 123,456,789 ▔▔ writefln!"%,*s"(6, 123456789); // 123,456789 writefln!"%,?s"('·', 123456789); // 123·456·789 writefln!"%,*?s"(2, '`', 123456789); // 1`23`45`67`89

56 / 121

slide-57
SLIDE 57

std.parallelism.parallel

One of the most impressive parts of the D standard library.

57 / 121

slide-58
SLIDE 58

std.parallelism.parallel

One of the most impressive parts of the D standard library. Assuming that the following takes 4 seconds on a single core:

foreach (e; elements) { // ... }

58 / 121

slide-59
SLIDE 59

std.parallelism.parallel

One of the most impressive parts of the D standard library. Assuming that the following takes 4 seconds on a single core:

foreach (e; elements) { // ... }

The following takes 1 second on 4 cores:

foreach (e; elements.parallel) { // ... }

59 / 121

slide-60
SLIDE 60

std.parallelism.parallel (continued)

Impressive because parallel is not a language feature:

60 / 121

slide-61
SLIDE 61

std.parallelism.parallel (continued)

Impressive because parallel is not a language feature:

  • A function that returns an object,

61 / 121

slide-62
SLIDE 62

std.parallelism.parallel (continued)

Impressive because parallel is not a language feature:

  • A function that returns an object,
  • which defines opApply to support foreach iteration,

62 / 121

slide-63
SLIDE 63

std.parallelism.parallel (continued)

Impressive because parallel is not a language feature:

  • A function that returns an object,
  • which defines opApply to support foreach iteration,
  • which distributes the loop body to a thread pool,

63 / 121

slide-64
SLIDE 64

std.parallelism.parallel (continued)

Impressive because parallel is not a language feature:

  • A function that returns an object,
  • which defines opApply to support foreach iteration,
  • which distributes the loop body to a thread pool,
  • and waits for their completion.

64 / 121

slide-65
SLIDE 65

std.parallelism.parallel (continued)

Impressive because parallel is not a language feature:

  • A function that returns an object,
  • which defines opApply to support foreach iteration,
  • which distributes the loop body to a thread pool,
  • and waits for their completion.

Impressive also because the guideline list is short:

  • 1. Make sure loop body is independent for each element.

65 / 121

slide-66
SLIDE 66

std.parallelism.parallel (continued)

int[] results; foreach (e; elements.parallel) { results ~= process(e); // ← BUG reportProgress(/* ... */); // ← Questionable }

66 / 121

slide-67
SLIDE 67

std.parallelism.parallel (continued)

int[] results; foreach (e; elements.parallel) { results ~= process(e); // ← BUG reportProgress(/* ... */); // ← Questionable }

One way of fixing the bug:

auto results = new int[elements.length]; // Separate result per element foreach (i, e; elements.parallel) { results[i] = process(e); // ... }

67 / 121

slide-68
SLIDE 68

std.parallelism.parallel (continued)

int[] results; foreach (e; elements.parallel) { results ~= process(e); // ← BUG reportProgress(/* ... */); // ← Questionable }

One way of fixing the bug:

auto results = new int[elements.length]; // Separate result per element foreach (i, e; elements.parallel) { results[i] = process(e); // ... } Warning: See "false sharing", which may hurt performance here.

68 / 121

slide-69
SLIDE 69

std.parallelism.parallel (continued)

One way of reporting progress correctly:

size_t completed = 0; foreach (i, e; elements.parallel) { // ... synchronized { // ← QUESTIONABLE completed++; reportProgress(completed, elements.length); } }

69 / 121

slide-70
SLIDE 70

std.parallelism.parallel (continued)

One way of reporting progress correctly:

size_t completed = 0; foreach (i, e; elements.parallel) { // ... synchronized { // ← QUESTIONABLE completed++; reportProgress(completed, elements.length); } }

Perhaps, needing reportProgress() is proof that process(e) takes a long time anyway and synchronized is affordable? Only you can decide...

70 / 121

slide-71
SLIDE 71

std.parallelism.parallel (continued)

T wo configuration points:

  • 1. Thread count: parallel distributes to totalCPUs

number of threads by default. T

  • change:
  • Create a TaskPool with desired thread count, which

you must finish().

71 / 121

slide-72
SLIDE 72

std.parallelism.parallel (continued)

T wo configuration points:

  • 1. Thread count: parallel distributes to totalCPUs

number of threads by default. T

  • change:
  • Create a TaskPool with desired thread count, which

you must finish().

  • 2. Work unit size: Each thread grabs execution of 100 elements by
  • default. T
  • change:
  • Specify a work unit size (e.g. 1 for loop bodies that take a long

time).

72 / 121

slide-73
SLIDE 73

std.parallelism.parallel (continued)

T wo configuration points:

  • 1. Thread count: parallel distributes to totalCPUs

number of threads by default. T

  • change:
  • Create a TaskPool with desired thread count, which

you must finish().

  • 2. Work unit size: Each thread grabs execution of 100 elements by
  • default. T
  • change:
  • Specify a work unit size (e.g. 1 for loop bodies that take a long

time).

auto tp = new TaskPool(totalCPUs / 2); // 1. Thread count foreach (e; tp.parallel(elements, 1)) { // 2. Work unit size // ... } tp.finish(); // Don't forget

Experiment with different combinations for best performance for your loop.

73 / 121

slide-74
SLIDE 74

std.concurrency

Message passing concurrency is

  • The right kind of concurrency for many programs
  • More complicated than parallelism

My recipe follows...

74 / 121

slide-75
SLIDE 75

std.concurrency

Message passing concurrency is

  • The right kind of concurrency for many programs
  • More complicated than parallelism

My recipe follows... Start a thread with spawnLinked:

auto workers = 4.iota .map!(i => spawnLinked(&workerThread)) .array; // ... void workerThread() { // ... }

75 / 121

slide-76
SLIDE 76

std.concurrency

Message passing concurrency is

  • The right kind of concurrency for many programs
  • More complicated than parallelism

My recipe follows... Start a thread with spawnLinked:

auto workers = 4.iota .map!(i => spawnLinked(&workerThread)) .array; // ... void workerThread() { // ... }

  • Send messages with send
  • Wait for messages with receive (or receiveTimeout)

76 / 121

slide-77
SLIDE 77

std.concurrency (continued)

Detect thread termination with a LinkTerminated message:

size_t completed = 0; while (completed < workers.length) { receive( (const(LinkTerminated) msg) { completed++; }, // ... ); } }

Note: There is also OwnerTerminated. 77 / 121

slide-78
SLIDE 78

std.concurrency (continued)

Threads have separate function call stacks

1.

  • Each worker must catch and communicate its

exceptions.

1. http://dconf.org/2016/talks/cehreli.html

78 / 121

slide-79
SLIDE 79

std.concurrency (continued)

Threads have separate function call stacks

1.

  • Each worker must catch and communicate its

exceptions.

void workerThread() { try { workerThreadImpl(); // Dispatch to the implementation } catch /* ... */ } void workerThreadImpl() { // ... }

1. http://dconf.org/2016/talks/cehreli.html

79 / 121

slide-80
SLIDE 80

Exception kinds

Throwable (do not catch) ↗ ↖ Exception Error (do not catch) ↗ ↖ ↗ ↖ ... ... ... ...

80 / 121

slide-81
SLIDE 81

Exception kinds

Throwable (do not catch) ↗ ↖ Exception Error (do not catch) ↗ ↖ ↗ ↖ ... ... ... ...

Exception: Something bad happened but the program is in a recoverable state.

enforce(!name.empty, "Name cannot be empty.");

  • May catch and continue

81 / 121

slide-82
SLIDE 82

std.concurrency (continued)

Reporting Exception:

struct WorkerError { int id; immutable(Exception) exc; }

82 / 121

slide-83
SLIDE 83

std.concurrency (continued)

Reporting Exception:

struct WorkerError { int id; immutable(Exception) exc; } void workerThread() { try /* ... */ catch (Exception exc) {

  • wnerTid.send(WorkerError(id, cast(immutable)exc));

} // ... }

83 / 121

slide-84
SLIDE 84

std.concurrency (continued)

Reporting Exception:

struct WorkerError { int id; immutable(Exception) exc; } void workerThread() { try /* ... */ catch (Exception exc) {

  • wnerTid.send(WorkerError(id, cast(immutable)exc));

} // ... } receive( (const(WorkerError) msg) { // ... }, // ... );

84 / 121

slide-85
SLIDE 85

Error

The program is in an illegal state.

assert(name.length == 42, format!"Wrong name: %s"(name));

  • Should not catch() (in theory)
  • Should not format() (in theory)
  • Should not abort() (in theory)
  • Should not do anything (in theory)

85 / 121

slide-86
SLIDE 86

Error

The program is in an illegal state.

assert(name.length == 42, format!"Wrong name: %s"(name));

  • Should not catch() (in theory)
  • Should not format() (in theory)
  • Should not abort() (in theory)
  • Should not do anything (in theory)

One practical approach applied by D runtime for the main thread:

  • 1. Catch
  • 2. Report
  • 3. Abort

See: rt_trapExceptions and --DRT-trapExceptions=0

1 for changing the behavior of the main thread.

1. http://arsdnet.net/this-week-in-d/2016-aug-07.html

86 / 121

slide-87
SLIDE 87

std.concurrency (continued)

Reporting Error:

void workerThread() { try /* ... */ catch (Error err) { // Contrary to theory stderr.writeln(err); // Wishful thinking: Does stderr even exist? import core.stdc.stdlib; abort(); } }

87 / 121

slide-88
SLIDE 88

std.concurrency (continued)

Passing mutable data between threads:

auto workers = 4.iota .map!(i => spawnLinked(&workerThread, cast(shared)new int[42])) .array;

Note: immutable data is implicitly shared (e.g. string). 88 / 121

slide-89
SLIDE 89

std.concurrency (continued)

Passing mutable data between threads:

auto workers = 4.iota .map!(i => spawnLinked(&workerThread, cast(shared)new int[42])) .array;

Note: immutable data is implicitly shared (e.g. string).

Worker thread must take shared and likely cast it away:

void workerThread(shared(int[]) data) { // Take shared try { workerThreadImpl(cast(int[])data); // Cast shared away } // ... } void workerThreadImpl(int[] data) { // Non-shared happily ever after // ... }

89 / 121

slide-90
SLIDE 90

std.concurrency (continued)

Passing mutable data between threads:

auto workers = 4.iota .map!(i => spawnLinked(&workerThread, cast(shared)new int[42])) .array;

Note: immutable data is implicitly shared (e.g. string).

Worker thread must take shared and likely cast it away:

void workerThread(shared(int[]) data) { // Take shared try { workerThreadImpl(cast(int[])data); // Cast shared away } // ... } void workerThreadImpl(int[] data) { // Non-shared happily ever after // ... }

  • Warning: Do not actually share this data between threads!

90 / 121

slide-91
SLIDE 91

import std; // Importing the entire package for terseness. void main() { auto workers = 4.iota .map!(id => spawnLinked(&workerThread, id, cast(shared)new int[42])) .array; size_t completed = 0; while (completed != workers.length) { receive( (const(LinkTerminated) msg) { completed++; }, (const(WorkerError) msg) { writefln!"Worker %s failed: %s"(msg.id, msg.exc.msg); }, (const(WorkerReport) msg) { writefln!"Worker %s finished successfully with %s."(msg.id, msg.data); }, ); } } struct WorkerError { int id; immutable(Exception) exc; } void workerThread(int id, shared(int[]) data) { try { workerThreadImpl(id, cast(int[])data); // Dispatch to the implementation } catch (Exception exc) {

  • wnerTid.send(WorkerError(id, cast(immutable)exc));

} catch (Error err) { stderr.writeln(err); import core.stdc.stdlib : abort; abort(); } } struct WorkerReport { int id; int data; } void workerThreadImpl(int id, int[] data) { foreach (d; data) { // We will fail with some probability failMaybe(id, data.length); } // Survived without an error; send report.

  • wnerTid.send(WorkerReport(id, 42));

} // This function simulates an operation that may fail void failMaybe(int id, size_t length) { auto msg(string kind) { return format!"Worker %s is throwing %s."(id, kind); } // Succeeds most of the time final switch (dice(length * 5, 1, 1)) { case 0: break; case 1: enforce(false, msg("Exception")); break; case 2: assert(false, msg("Error")); break; } }

std.concurrency (continued)

Single-slide example. :o) Each worker thread either succeeds or fails with either Exception or Error.

91 / 121

slide-92
SLIDE 92

Nested functions

void foo() { foreach (i; 0 .. n) { if (a[i].p.q.r.color == "red" && b[i].p.q.r.color == "green") { // ... enforce(c, format!"illegal: %s"(a[i].p.q.r.color)); } } }

92 / 121

slide-93
SLIDE 93

Nested functions

void foo() { foreach (i; 0 .. n) { if (a[i].p.q.r.color == "red" && b[i].p.q.r.color == "green") { // ... enforce(c, format!"illegal: %s"(a[i].p.q.r.color)); } } }

Nested function for reducing code duplication and readability:

void foo() { foreach (i; 0 .. n) { auto color(S[] arr) { // Nested function return arr[i].p.q.r.color; // Using 'i' from the enclosing scope } if (color(a) == "red" && color(b) == "green") { // ... enforce(c, format!"illegal: %s"(color(a))); } } }

93 / 121

slide-94
SLIDE 94

Nested functions (continued)

struct RGB { ubyte red; ubyte green; ubyte blue; this(uint value) ubyte popLowByte() { ubyte b = value & 0xff; // Uses 'value' from the enclosing scope value >>= 8; return b; } this.blue = popLowByte(); this.green = popLowByte(); this.red = popLowByte(); } }

94 / 121

slide-95
SLIDE 95

Nested functions (continued)

void foo() { // The message is evaluated lazily: GOOD enforce(a, format!"illegal: %s"(x)); // Code duplication: BAD enforce(b, format!"illegal: %s"(x)); }

95 / 121

slide-96
SLIDE 96

Nested functions (continued)

void foo() { // The message is evaluated lazily: GOOD enforce(a, format!"illegal: %s"(x)); // Code duplication: BAD enforce(b, format!"illegal: %s"(x)); }

Not good enough:

const msg = format!"illegal: %s"(x); // Evaluated eagerly: BAD enforce(a, msg); enforce(b, msg); // No code duplication: GOOD

96 / 121

slide-97
SLIDE 97

Nested functions (continued)

void foo() { // The message is evaluated lazily: GOOD enforce(a, format!"illegal: %s"(x)); // Code duplication: BAD enforce(b, format!"illegal: %s"(x)); }

Not good enough:

const msg = format!"illegal: %s"(x); // Evaluated eagerly: BAD enforce(a, msg); enforce(b, msg); // No code duplication: GOOD

Nested function for lazy evaluation:

auto msg() { return format!"illegal: %s"(x); } enforce(a, msg); enforce(b, msg);

97 / 121

slide-98
SLIDE 98

Unmentionable types of range objects

Can't spell out unmentionable types:

struct S { ??? r; this(string fileName) { this.r = File(fileName).byLine; } }

98 / 121

slide-99
SLIDE 99

Unmentionable types of range objects

Can't spell out unmentionable types:

struct S { ??? r; this(string fileName) { this.r = File(fileName).byLine; } }

One solution is to return the expression from a function:

auto makeRange(string fileName = null) // ← Defaulted for convenience in (!fileName.empty) { // ← Checked against null return File(fileName).byLine; }

99 / 121

slide-100
SLIDE 100

Unmentionable types of range objects

Can't spell out unmentionable types:

struct S { ??? r; this(string fileName) { this.r = File(fileName).byLine; } }

One solution is to return the expression from a function:

auto makeRange(string fileName = null) // ← Defaulted for convenience in (!fileName.empty) { // ← Checked against null return File(fileName).byLine; } struct S { typeof(makeRange()) r; this(string fileName) { this.r = makeRange(fileName); } }

100 / 121

slide-101
SLIDE 101

Initializing a non-mutable variable

A known technique from other languages; nothing special about D here:

auto a = someValue(); if (condition) { doSomethingElse(); a = someOtherValue(); }

Trouble: a cannot be const or immutable.

101 / 121

slide-102
SLIDE 102

Initializing a non-mutable variable

A known technique from other languages; nothing special about D here:

auto a = someValue(); if (condition) { doSomethingElse(); a = someOtherValue(); }

Trouble: a cannot be const or immutable. Putting the whole logic into a lambda is a solution:

const a = { if (condition) { doSomethingElse(); return someOtherValue(); } return someValue(); }();

102 / 121

slide-103
SLIDE 103

std.typecons.Flag

T yped flags instead of bool:

void foo(Flag!"compress" compress, Flag!"skip" skip) { // ... } foo(Yes.compress, No.skip);

103 / 121

slide-104
SLIDE 104

std.typecons.Flag

T yped flags instead of bool:

void foo(Flag!"compress" compress, Flag!"skip" skip) { // ... } foo(Yes.compress, No.skip);

Checked at compile time:

foo(No.skip, Yes.compress); // ← compilation ERROR; good

104 / 121

slide-105
SLIDE 105

std.typecons.Flag

T yped flags instead of bool:

void foo(Flag!"compress" compress, Flag!"skip" skip) { // ... } foo(Yes.compress, No.skip);

Checked at compile time:

foo(No.skip, Yes.compress); // ← compilation ERROR; good

However, passing existing bool is not pleasant:

bool compress; bool skip; foo(compress ? Yes.compress : No.compress, skip ? Yes.skip : No.skip);

Note: Other options are not pleasant either. 105 / 121

slide-106
SLIDE 106

std.typecons.Flag (continued)

One solution is alias template parameters:

auto toFlag(alias variable)() { enum name = variable.stringof; mixin ("return variable ? Yes." ~ name ~ " : No." ~ name ~ ";"); }

106 / 121

slide-107
SLIDE 107

std.typecons.Flag (continued)

One solution is alias template parameters:

auto toFlag(alias variable)() { enum name = variable.stringof; mixin ("return variable ? Yes." ~ name ~ " : No." ~ name ~ ";"); } bool compress; bool skip; // Types are Flag!"compress" and Flag!"skip": foo(toFlag!compress, toFlag!skip);

107 / 121

slide-108
SLIDE 108

Module as a "singleton" object

Assume a top-level module function:

void topLevel(int[] a, string[] s) { // ... calls a graph of dozens of other functions ... }

108 / 121

slide-109
SLIDE 109

Module as a "singleton" object

Assume a top-level module function:

void topLevel(int[] a, string[] s) { // ... calls a graph of dozens of other functions ... }

Assume a new variable is introduced:

void topLevel(int[] a, string[] s, Flag!"verbose" verbose) { // ... }

109 / 121

slide-110
SLIDE 110

Module as a "singleton" object

Assume a top-level module function:

void topLevel(int[] a, string[] s) { // ... calls a graph of dozens of other functions ... }

Assume a new variable is introduced:

void topLevel(int[] a, string[] s, Flag!"verbose" verbose) { // ... }

Now dozens of function signatures may need to be modified:

void foo(int i, Flag!"verbose" verbose) { // ... } void bar(double d, Flag!"verbose" verbose) { // ... } // ... many more ...

110 / 121

slide-111
SLIDE 111

Module as a "singleton" object (continued)

A solution is to use a module variable:

Flag!"verbose" verbose; void topLevel(int[] a, string[] s, Flag!"verbose" verbose) { .verbose = verbose; // ... } void foo(int i) { // No need to change // ... } void bar(double d) { // No need to change // ... }

111 / 121

slide-112
SLIDE 112

Module as a "singleton" object (continued)

A solution is to use a module variable:

Flag!"verbose" verbose; void topLevel(int[] a, string[] s, Flag!"verbose" verbose) { .verbose = verbose; // ... } void foo(int i) { // No need to change // ... } void bar(double d) { // No need to change // ... }

BUG: Only this thread is affected! T

  • make it "per program":

112 / 121

slide-113
SLIDE 113

Module as a "singleton" object (continued)

A solution is to use a module variable:

Flag!"verbose" verbose; void topLevel(int[] a, string[] s, Flag!"verbose" verbose) { .verbose = verbose; // ... } void foo(int i) { // No need to change // ... } void bar(double d) { // No need to change // ... }

BUG: Only this thread is affected! T

  • make it "per program":

shared Flag!"verbose" verbose;

113 / 121

slide-114
SLIDE 114

Module as a "singleton" object (continued)

A solution is to use a module variable:

Flag!"verbose" verbose; void topLevel(int[] a, string[] s, Flag!"verbose" verbose) { .verbose = verbose; // ... } void foo(int i) { // No need to change // ... } void bar(double d) { // No need to change // ... }

BUG: Only this thread is affected! T

  • make it "per program":

shared Flag!"verbose" verbose; Warning: : May not work as desired if topLevel is called multiple times.

114 / 121

slide-115
SLIDE 115

Parsing files at compile time

Requirements:

  • A program that parses a file at run time

$ my_program file.txt

115 / 121

slide-116
SLIDE 116

Parsing files at compile time

Requirements:

  • A program that parses a file at run time

$ my_program file.txt

  • The file should be optional

$ my_program ← Use default content

116 / 121

slide-117
SLIDE 117

Parsing files at compile time

Requirements:

  • A program that parses a file at run time

$ my_program file.txt

  • The file should be optional

$ my_program ← Use default content

Boring in D: The same function for compile time and run time.

// Returns significant lines of the content string[] parse(string content) { auto isComment = (string line) => line.startsWith('#'); auto isSignificant = (string line) => !line.empty && !isComment(line); return content .splitter('\n') // Split by lines .map!strip // Strip whitespace .filter!isSignificant .array; }

117 / 121

slide-118
SLIDE 118

Parsing files at compile time (continued)

import expression to read a file at compile time:

immutable defaultList = parse(import("default_file.txt"));

118 / 121

slide-119
SLIDE 119

Parsing files at compile time (continued)

import expression to read a file at compile time:

immutable defaultList = parse(import("default_file.txt"));

Alternatives:

static const defaultList = /* ... */; enum defaultList = /* ... */; // Generally more costly at run time

119 / 121

slide-120
SLIDE 120

Parsing files at compile time (continued)

import expression to read a file at compile time:

immutable defaultList = parse(import("default_file.txt"));

Alternatives:

static const defaultList = /* ... */; enum defaultList = /* ... */; // Generally more costly at run time

User code:

void main(string[] args) { auto theList = (args.length == 1 ? defaultList : args[1].readText.parse); // ... }

120 / 121

slide-121
SLIDE 121

Conclusion

  • D is a powerful engineering tool.
  • D is very productive.
  • D is very much fun.

121 / 121