Parallel Programming with Thread pools and iterators 1. About me - - PowerPoint PPT Presentation

parallel programming with thread pools and iterators
SMART_READER_LITE
LIVE PREVIEW

Parallel Programming with Thread pools and iterators 1. About me - - PowerPoint PPT Presentation

Stefan Schindler (@dns2utf8) March 20, 2019 Rust Zrichsee, Schweiz, CH - hosted by Cloud Solutions Amsterdam, NL Parallel Programming with Thread pools and iterators 1. About me 2. Loops 3. Iterators 4. Modes of Execution 5.


slide-1
SLIDE 1

Parallel Programming with Thread pools and iterators

Stefan Schindler (@dns2utf8) March 20, 2019

Rust Zürichsee, Schweiz, CH - hosted by Cloud Solutions Amsterdam, NL

slide-2
SLIDE 2

Index

  • 1. About me
  • 2. Loops
  • 3. Iterators
  • 4. Modes of Execution
  • 5. Implementation
  • 6. Receipt: from Loops to Iterators
  • 7. Questions
slide-3
SLIDE 3

About me

slide-4
SLIDE 4

Timetable

  • 18:30 => Venue opens, pizza’s arrive
  • now => Talk Stefan: Parallel Programming with Rust
  • 19:30 => Break
  • 19:45 => Maarten: How to speed up your builds with Bazel
  • 20:15 => Discussions
  • 21:00 => Venue closes
  • tomorrow => ???
  • the day aħter => parallelize the World!

1/29

slide-5
SLIDE 5

About:me

Hello my name is Stefan and I work on and with computers. I organize

  • RustFest.eu Next: probably in November 2019 with ”impl days” before or aħter

the conference

  • Meetups in and around Zürich, CH
  • ErnstEisprung.ch (in de Zwitserse Alpen Juli 2019)

Some of my side projects

  • rust threadpool (maintainer)
  • Son of Grid Engine (SGE) interface
  • run your own infrastructure - DNS, VPN, Web, ...

2/29

slide-6
SLIDE 6

What will we learn tonight?

  • Loops
  • Iterators
  • Difgerent modes of execution
  • Single vs. Multi Threading
  • How to synchronize pools
  • Hot to translate linear into parallel code

3/29

slide-7
SLIDE 7

Loops

slide-8
SLIDE 8

Loops 0 - What happened so far

const char *data[] = { "Peter Arbeitsloser", ... }; const int length = sizeof(data) / sizeof(data[0]); int index = 0; head: if (!(index < length)) { goto end; } const char *name = data[index]; printf("%i: %s\n", index, name); index += 1; goto head; end:

4/29

slide-9
SLIDE 9

Loops 1 - What improved

const char *data[] = { "Peter Arbeitsloser", "Sandra Systemadministratorin", "Peter Koch", }; const int length = sizeof(data) / sizeof(data[0]); for (int index = 0; index < length; ++index) { const char *name = data[index]; printf("%i: %s\n", index, name); }

5/29

slide-10
SLIDE 10

Loops 2 - What happens in rust

For the following slides keep this in mind: #[allow(non_upper_case_globals)] const data: [&str; 3] = [ "Peter Arbeitsloser", "Sandra Systemadministratorin", "Peter Koch", ];

6/29

slide-11
SLIDE 11

Loops 3 - While

let mut index = 0; let length = data.len(); while index < length { println!("{}: {}", index, data[index]); index += 1 }

7/29

slide-12
SLIDE 12

Loops 4 - For each

for name in &data { println!("{}", name); } Note the & next to data.

8/29

slide-13
SLIDE 13

Loops 5 - Iterator

for name in data.iter() { println!("{}", name); } If we prefer a more functional style: let iterator = data.iter(); iterator.for_each(|name| { println!("{}", name); });

9/29

slide-14
SLIDE 14

Iterators

slide-15
SLIDE 15

Trait Iterator

// std::iter::Iterator pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; } // For reference std::option::Option pub enum Option<T> { None, Some(T), }

10/29

slide-16
SLIDE 16

Iterators 0

let iterator = data.iter(); iterator.for_each(|name| { println!("{}", name); });

  • Why?
  • Pros for
  • People programming (filters, maps, maintainability, ...)
  • Compiler (optimizations, early returns, edge cases, ...)

Video (32min): RustFest Rome 2018 - Pascal Hertleif: Declarative programming in Rust

  • media.ccc.de/v/rustfest-rome-5-declarative-programming-in-rust
  • youtube.com/watch?v=0W20GPEqbcU

11/29

slide-17
SLIDE 17

Iterators 1 - Parsing without panic

struct Person { first_name: String, surname: String, } let processed = data .iter() .map(|name| { let mut split = name.split(" "); let (first_name, surname) = (split.next(), split.next()); if first_name.is_none() || surname.is_none() { return Err("Unable to parse: to few parts") } Ok(Person { first_name: first_name.unwrap().into(), surname: surname.unwrap().into(), }) }) .collect::<Result<Vec<_>, _>>();

12/29

slide-18
SLIDE 18

Iterators 2 - Parsing without panic

struct Person { first_name: String, surname: String, } let processed = data.iter() .map(|name| { let mut split = name.split(" "); let (first_name, surname) = (split.next(), split.next()); match (first_name, surname) { (Some(first_name), Some(surname)) => { Ok(Person { first_name: first_name.into(), surname: surname.into(), }) } _ => { Err("Unable to parse: to few parts") } } }) .collect::<Result<Vec<_>, _>>(); // <- magic happened

13/29

slide-19
SLIDE 19

Iterators 3 - ”processed: {:#?}”

processed: Ok( [ Person { first_name: "Peter", surname: "Arbeitsloser" }, Person { first_name: "Sandra", surname: "Systemadministratorin" }, Person { first_name: "Peter", surname: "Koch" } ] )

14/29

slide-20
SLIDE 20

Modes of Execution

slide-21
SLIDE 21

Programming is ...

... about solving problems Examples:

  • Copy data
  • Enhance audio
  • Distribute messages
  • Store data
  • Prepare thumbnails

Key is understanding the problem

15/29

slide-22
SLIDE 22

Single thread - Linear Execution

How to do more than one thing at the time?

  • Linear if tasks are short enough
  • Polling
  • Event driven (select/epoll or interrupt)
  • Hardware SIMD

16/29

slide-23
SLIDE 23

Simultaneous Multi Threading - SMP

Let’s add another level of abstraction

  • spawn / join: handle lists of JoinHandles
  • pools
  • job queue (threadpool)
  • Work stealing (rayon)
  • futures (tokio or async/await)

New problems: synchronization and communication

17/29

slide-24
SLIDE 24

Implementation

slide-25
SLIDE 25

Send and Sync

Rusts ”pick three” (safety, speed, concurrency) Trait std::marker::Send Types that can be transferred across thread boundaries. Trait std::marker::Sync Types for which it is safe to share references between threads.

18/29

slide-26
SLIDE 26

Crates

Let’s reuse that level of abstraction

  • std::thread::spawn, join
  • pools
  • ThreadPool (Job Queue)
  • FuturesThreadPool (Work stealing)
  • rayon (Work stealing)
  • timely dataflow (distributed actor model)

New problems: synchronization and communication

19/29

slide-27
SLIDE 27

Channel example

use threadpool::ThreadPool; use std::sync::mpsc::channel; let n_workers = 4; let n_jobs = 8; let pool = ThreadPool::new(n_workers); let (tx, rx) = channel(); for _ in 0..n_jobs { let tx = tx.clone(); pool.execute(move || { tx.send(1).expect("channel will be there"); }); } drop(tx); // <- Why? assert_eq!(rx.iter() /*.take(n_jobs)*/ .sum() , /* n_jobs = */ 8);

20/29

slide-28
SLIDE 28

Channel cascade example

let (tx, mut rx) = channel(); tx.send( (0, 0) ).is_ok(); for _ in 0..TEST_TASKS { let rx_pre = rx; let (tx_chain, rx_chain) = channel(); rx = rx_chain; pool.execute(move || { let r = pi_approx_random(TRIES as u64 , rand::random::<f64>); let b = rx_pre.recv().unwrap(); tx_chain.send( (b.0 + r.0, b.1 + r.1) ).is_ok(); }); } println!("chain.pi: {}", format_pi_approx(rx.recv().unwrap()));

21/29

slide-29
SLIDE 29

Receipt: from Loops to Iterators

slide-30
SLIDE 30

Collect from Channel - 0

v_len holds the number of elements we expect let mut pictures = vec![]; for _ in 0..v_len { if let Some(pi) = rx.recv().unwrap() { pictures.push( pi ); } else { // Abort because of error return; } }

22/29

slide-31
SLIDE 31

Collect from Channel - 1

With iter() we don’t need to know the length anymore let mut pictures = vec![]; for pi in rx.iter() { if let Some(pi) = pi { pictures.push( pi ); } else { // Abort because of error return; } }

23/29

slide-32
SLIDE 32

Collect from Channel - 2

With for_each(...) we don’t need to know the length anymore let mut pictures = vec![]; rx.iter().for_each(|pi| { if let Some(pi) = pi { pictures.push( pi ); } else { // Abort because of error return; } });

24/29

slide-33
SLIDE 33

Collect from Channel - 3

Use map and collect let pictures = rx.iter().map(|pi| { if let Some(pi) = pi { Ok( pi ) } else { // Abort because of error println("our custom error message"); Err( () ) } }) .collect::<Result<Vec<PictureInfo>, ()>>() .unwrap();

25/29

slide-34
SLIDE 34

Collect from Channel - 4

Move the error message out let pictures = rx.iter().map(|pi| { if let Some(pi) = pi { Ok( pi ) } else { // Abort because of error Err("our custom error message") } }) .collect::<Result<Vec<PictureInfo>, ()>>() .expect("unable to iterate trough pictures");

26/29

slide-35
SLIDE 35

Collect from Channel - 5

Parallelize with rayon let pictures = rx.par_iter().map(|pi| { if let Some(pi) = pi { Ok( pi ) } else { // Abort because of error Err("our custom error message") } }) .collect::<Result<Vec<PictureInfo>, ()>>() .expect("unable to iterate trough pictures");

27/29

slide-36
SLIDE 36

Questions

slide-37
SLIDE 37

Thank you for your attention!

Stefan Schindler @dns2utf8 Happy hacking! Please ask questions!

Slides & Examples: https://github.com/dns2utf8/thread-pools-and-iterators

slide-38
SLIDE 38

background.pdf

Why another language? - 0

  • It is hard to write safe and correct code.
  • Even harder to write correct parallel code.

char *pi = "3.1415926f32"; while(1) { printf("Nth number? "); err = scanf("%d", &nth); if (err == 0 || errno != 0) { printf("invalid entry\n"); while (getchar() != '\n'); continue; } printf("Input: %d\n", nth); printf("Gewünschte Stelle: '%c'\n", pi[nth]); }

28/29

slide-39
SLIDE 39

background.pdf

Why another language? - 1

let pi = "3.1415926f32"; loop { print!("Nth number? "); io::stdout().flush().unwrap(); // force display on terminal let mut input = String::new(); match io::stdin().read_line(&mut input) { Ok(_bytes_read) => { let nth: usize = input.trim().parse() .expect("invalid selection"); println!("{}-th: '{:?}'", nth, pi.chars().nth(nth)); } Err(error) => println!("error: {}", error), } }

29/29