You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
By-name parameters are only evaluated when used. They are in contrast to by-value parameters. To make a parameter called by-name, simply prepend => to its type.
1.5 Square root using Newton's method
Recursive functions NEED to have a return type (because it needs to know the return type), otherwise it is optional (Duh)
1.6 Blocks and Lexical Scope
Nested functions -- put auxillary functions inside the function sqrt
In Java, arrays are covariant, but they do type checking for every array to prevent issues.
Why was it done? Because they wanted a sort to work for everything.
Barbara Liskov Substition Principle
If A<:B, then everything you can do with B, you should be able to do with A.
In scala, Arrays are NOT Covariant (for why, see more below)
4.4: Variance
Lists are immutable, arrays are mutable. Immutable types can be covariant, if some other additional conditions are also met.
Types can be covariantC[+A], contravariantC[-A], or nonvariantC[A]
Typing rules for functions --
For a function, if A2 <: A1 and B1 <: B2, then A1 => B1 <: A2 => B2.
Functions must be contravariant in their argument types and covariant in their result types
Example -->
trait Function1[-T, +U] {
def apply(x: T): U
} // Variance check is OK because T is contravariant and U is covariant
class Array[+T] {
def update(x: T)
} // variance checks fails
MIND BLOWN! --> Function trait (checked in Scala code) becomes
trait Function1[-T, +U] {
def apply(x: T) : U
}
i.e. return type is covariant, argument type is contravariant.
Can you just put + and - whenever the hell you want?! NOOO -- otherwise we could make array covariant. Scala compiler checks that are no problematic rules of variance.
. Roughly, what it will do is, it will let covariant type parameters only appear in method results. Contravariant type parameters can only appear in method parameters, and nonvariant, or invariant, that they are used as aliases, type parameters can actually appear anywhere.
Takes example of Nil from the Cons Lists example.Nothing is a subtype of everything.
Making a class covariant can be a lot of work! -- The prepend example
Assume the similar Cat, Dog, Animal inheritance tree used earlier, plus the following:
abstract class SmallAnimal extends Animal
case class Mouse(name: String) extends SmallAnimal
Suppose we’re working with functions that accept types of animals, and return the types of food they eat. If we would like a Cat => SmallAnimal (because cats eat small animals), but are given a Animal => Mouse instead, our program will still work. Intuitively an Animal => Mouse will still accept a Cat as an argument, because a Cat is an Animal, and it returns a Mouse, which is also a SmallAnimal. Since we can safely and invisibly substitute the former by the latter, we can say Animal => Mouse is a subtype of Cat => SmallAnimal.
4.5 Decomposition
Expr, Number and Sum example; trait expr
Too tedious!! too many methods for any functionality. Quadratic number of methods with functionality.
Maybe use Type Casts? Nooo that sucks! Scala does not recommend it.
Define eval in every class -- but that way need to touch all classes if you add a method to the base trait.
4.6 Pattern Matching
All previous methods have shortcomings.
Case Classes
-- Automatically adds companion objects.
-- A generalization of the switch statement.
-- Can match on multiple expressions
-- Matches in the order they are written (Can match constructors too Sum(x1, x2)
-- You can have methods in the class/object itself this.match for example.
Is not always good! Analyze the use cases and decide.. The Expression Problem
4.7 Lists
Lists are immutable, and recursive
Similar to the Cons lists implemented before.
All lists are constructed from empty lists, using ::, called Cons eg. fruit = "apples" :: "(banana")List(x) == x :: Nil
Cons associated to the righ -- A::B::C is interpreted as A::(B::C)
Introduces Var ---> for signals that can be updated/changed (The Update method -- which is also defined for Arrays.
Important -- Defining a signal in terms of other Signal A = f(Signal B) for example -- the relation is maintained going forward. (forever in the future!)
Goes over the BankAccount example.
Balance becomes a Signal[Int]
A signal cannot be a function of itself!! balance() = balance() * amount IS WRONGG!!
Instead get the value of balance in a val and then do it
IMPORTANT!
Diff between b = b + 1 and b() = b() + 1 -- the latter DONT MAKE SENSE!, as the first one updated value, and the second defines a relation in eternity.
Lecture 4.3 - A simple FRP implementation
Every signal has (1) the current value (2) current expression defining it (3) all of its observers that need to re- evaluated; if signal value changes.
How to know on whose behalf a signal is updated.
Need to call in a stack like fasion -- defines a class Stackable variables
Type Signal[_] it means can take signals of all types.
NoSignal -- a sentinel signal.
Adds the current caller to the signal
A signal cannot observe itself (duh) -- Catches the cyclic dependency
computeValue() does the propagation
NoSignal's computeValue does nothing.
Problems with parallelism!!
Going to thread local -- Dynamic Variable
Pass current value of signal as a implicit parameter
Lecture 4.4 - Latency as an Effect 1
Introduces the monad Future (Computations can take time!!!)
Reading from memory takes time, and sending a packet to Europe takes time!
Converting into a human time format, that makes sense! --> sending a packet to Europe in this scale takes 5 years!
Do not have to wait for a computation, Callback -- mind blown!!*
Lecture 4.5 - Latency as an Effect 2
A Monad -- Future
Handles both the idea that computations can fail, and can also take time.
Function onComplete --> think of it as an envelope given to the calculator and asking him to find the result, fill the envelope and return it back to us.
Ignoring implicit execution context for now.
Function can match to Success or Failure
Can have a onComplete that takes two callbacks (one for success/failure)
readFromMemory returns Future[Array[Byte]]
Future's apply returns a future that computes asynchronously (Future just expects a Body)
Lecture 4.6 - Combinators on Futures 1
Functions on future.. all the usual flatMap etc, along with recoverWith
flatMap cleans up the code and makes it nicer -- Very nice examples!
zips two futures (Does not work as zip fails when one of the lists being zipped fails)
recover and recoverWith
Lecture 4.7 - Combinators on Futures 2
Writes a better method fallBackTo --> expressed in terms of recoverWith
Introduces blocking, the Awaitable trait, but IS NOT RECOMMENDED!!
You should NEVER EVER BLOCK in production!!
Lecture 4.8 - Composing Futures 1
for comprehensions (remember brightroll?!!)
Defines retry
In terms of fallBackTo recursion
Lecture 4.9 - Implementation of flatMap on Future
Implementation of flatMap in terms of CallBack
Lecture 4.10 - Composing Futures 2
Goes over reminding us of foldLeft and foldRight
Implements retry in terms of foldLeft and foldRight
Sometimes straight recursion is much simpler to understand!
Many calculations (can be done in parallel), same time
Amdahl!!
After a while, hit a power wall
Just increasing CPU frequency does not help, so added multi-cores!
Parallel programming is HARDER than sequential
But we need to do it for SPEEDUP (No other reason)
Parallel vs Concurrency
Parallel -- Uses Parallel hardware for more efficiency (eg. matrix multiplication, computer graphics) - speedup
Concurrent -- Improves modularity (eg. async apps such as web servers, databases) -- convenience
Both of these can intersect each other
Different granularities of Parallelism
Bit-level parallelism and Instruction-level parallelism and Task-level parallelism
Different types of hardware -- Multi core processors, Symmetric multiprocessors, GPU, FPGA, Computer Clusters
1.2 Parallelism on the JVM
What is an operating system? -- Software than manages hardware and software resources, and schedules program execution
Process - An instance of a program executing in the OS (a process has a PID) -- the most coarse grained unit in || programming
Multitasking -- two processes are isolated and owns a memory area
Each process can have multiple threads (Threads share memory space) -- can be started within the same program, and can exchange information
between one another
A thread has a program counter and a program stack
JVM process starts with main thread -- in a sequential program, only the main thread is used.
Goes over the Thread subclass
Goes over an example where the outputs can be different (threads are executed in parallel!)
To ensure, a sequence of statements is in executed in order -- Atomicity -- use the synchronized construct! -- at most one thread
can hold a monitor
1.3 Parallelism on the JVM II
Synchronizations can be nested! -- Gives the example of a bank transfer -- locks on both source and transfer account
Excellent example when a thread transfers from a1 to a2, and another from a2 to a1 --> DEADLOCK!
Resolving deadlocks
Acquire locks in the same order!
Memory model
How threads interact when accessing shared memory
Two threads writing to different memory locations --> no need to synchronize!
The thread join --> seeing write Rule.
1.4 Running Computations in Parallel
pNorm example
Uses recursion!!
MIND BLOWN! --> Need to use call by name! (need to pass unevaluated computations)
Memory contention!! -- Speedup also take into account not only cores, but also memory
Also you are only as fast as your fastest thread..
1.5 Estimate pi -- monte carlo
Estimate using randomly sampling points to fall within a circle/square
1.6 First class tasks
Using the construct task instead of parallel.
Defines Task interface that takes in a computation as an argument
Breaks the p-Norm implementation into tasks
Defines parallel construct in terms of tasks
1.7 How Fast are Parallel Programs?
Empirical measurement vs asymptotic analysis (worst case run time)
Asymptotic analysis of sequential running time and parallel running time on the sum-segment example
Parallel program time depends on the number of resources --> introduces Work (all sequential) and Depth (Infinite parallel)
Can never do better than Depth (as that is the case when parallelism is infinite)
Amdahl's law returns!! -- the law of parallelism
1.8 Benchmarking Parallel Programs
Benchmarking vs Testing
Testing is binary, benchmarking gives a continuous value
Just timing is not enough -- make sure to take care of stuff such as warm-up, eliminating outliers, prevent such as GC, JIT compilation, aggresive optimizations
Enter ScalaMeter
Be wary of GC! -- runtimes can vary across runs
JVM may apply further optimizations -- Scalameter has withWarmer -- awesome!! -- Nice intro to ScalaMeter; can also measure Memory footprint`
Assignment -- Nice assignment on blurring in photoshop
Week 2: Basic Task Parallel Algorithms
2.1 Parallel Sorting
Uses an intermediate array -- breaks down sorting into smaller array sizes
merge and the copy functions
parMergeSort vs quickSort -- Uses ScalaMeter
2.2 Data Operations and Parallel Mapping
Gives examples of operations on collections (fold, scan, map etc.)
List is NOT good (as cannot find the middle easily) --> use Array and Tree
The naive parallel implementation first element cons rest of list is still n!!
So we look at Array
Breaks into parallel implementation -- (1) Writes need to be disjoint (2) Threshold for sequential needs to be large enough to have efficiency!
Goes over implementation of Tree
leaf stores the num of elements, and nodes contain the elements
Compare arrays vs Trees --> in trees less worry about disjointness
2.3 Parallel Fold (Reduce) Operation
Goes over differences between foldLeft, foldRight, reduceLeft, reduceRight
Parallelism can be done ONLY on associative operations --- as we want to split into parts as we wish
Each associative operations can be shown as a tree --> leaves are the values and nodes are the operations.
For ordering for a Tree, can use list
Tree rotation preserves the result.
For arrays reduction, can convert into tree
Gives reduce and reduceSeg function. (reduce is done in a sequential fashion)
2.4 Associativity 1
Compares with commutative
Gives examples of operations that are both associative and commutative
Examples where it is associative but NOT commutative
String concat
Matrix multiplication
Example of where it is the opposite case
Should NOT LOSE associativity
Floating point operations are not necessarily associative! (Great examples!!) -- due to precision considerations
Trick to make functions commutative! --> get start and just always pass the lesser argument first (by making a less checker)
2.5 Associtavity II
Gives an example of associativity for tuples
Example of average using two reduces --> one for sum and other for length
Gives the case when commutativity also implies associativity (given the rotation condition)
Gives two examples of applying this theorem
Floating Numbers -- CUIDATE!!
Sets example (Closure operation -- A Closure B)
2.6 Scan
scan is like a combo of map and fold
HOW to PARALLELIZE?!!
this value Depends on the previous value!
Do more work (store the intermediate results) --> parallelize, totally worth it.
Have trees that store intermediate values
Think of summing up trees from leaf to the head, storing results in an intermediate fashion as you go up.
upsweep and downsweep functions to the rescue
Application to arrays --> store the Segment of array in the non-leaf nodes
Assignment -- Parallel programming
Count change problem -- how to divide stuff if we do not know the workload?!!
Counting change is a canonical example of a task-parallel problem in which the partitioning the workload across processors is solution-driven -- to know how to optimally partition the work, we would first need to solve the problem itself.
the synchronization costs of doing this are way too high. Instead, we need to agglomerate parts of the computation. We do this by calling the sequential countChange
Speed up is measured using ScalaMeter -- letting it warmup and GC first
Nice line of sight problem
Week 3: Data-parallelism
3.1 Data Parallel Programming
Task parallelization --> a form of parallelization that distributes execution across computing nodes
Data parallelization --> distributes data across nodes
Parallel for loop
Introduces the parallel for loop --> MUST BE CAREFUL of adding .par to the loop
Mandelbrot set --> Keeps computing complex numbers till reaching infitiny if adbsolute value
Goes over the image array in parallel.
Compares data parallelism for this problem, vs task parallelism --> Why?!! -- compares workload for different parts of the data
Task of balancing workload is managed by the scheduler -- we do not care about equally dividing! it is done for us..
3.2 Data Parallel Operations
Some methods like filter can be done in par.
But foldLeft CANNOT be done --> great lego example!! -- foldLeft depends on the previous part --> so MUST be sequential
But fold can be done--> it is like a reduction tree
3.3 Data Parallel Operations II
Uses fold to calculate max using Int.MinValue as the neutral element
Rock, paper and scissors example!
Play is NOT associative --> it is commutative
For fold to work --> the two following conditions must hold to make the operator f a MONOID
f(a, f(b,c)) == f(f(a,b),c) -- ASSOCIATIVE
f(z,a) == f(a,z) == a -- Like an identity on the neutral element z
Commutativity does not matter
For fold -- the type of the neutral element must be the same !!!
In comes aggregate --> a combination of fold and foldLeft
z A z A z A z A
\ / \ /seqop\ / \ /
B B B B
\ / combop \ /
B _ _ B
\ combop /
B
K-means is an example of a bulk synchronous parallel algorithm (BSP). BSP algorithms are composed
from a sequence of supersteps, each of which contains:
parallel computation, in which processes independently perform
local computations and produce some values;
communication, in which processes exchange data;
barrier synchronisation, during which processes wait until every process finishes
The recursive call should be the last thing that the function does. (no operation after!)
Assignment tries different choices of initial means selection and compares them
Week 4: Data Structures for parallel computing
4.1 - Implementing Combiners
Transformer methods --> like filter, map etc (create new collection)
Scala Builder for sequential transformer operations
For Parallel, we use Combiner (a Builder with a combine) (combinethis with that)
Combine is different for different collections (for Set and Map it is a union)
Combine MUST be efficient!! Must execute in O(log n + log m) time
To gain any speedup, the operation must be logarithmic!!
Combiner
Gives a bad implementation of Combiner --> O(n)
Arrays CANNOT be combined efficiently --> as they are contiguous
Compares runtime for various Data Structures.
Pues.. implementation of a combiner is NOT trivial!
4.2 Parallel Two-phase Construction
Construct data structures in parallel using the two-phase construction (Use an intermediate data structure)
The intermediate data structure must have an efficient combine method, an efficient += method and can convert into the resulting data structure in O(n/P) time
Many intermediate data structures are created ,and then combined, and then parallel-y it is converted to the initial daa structure
Uses a nested array buffer -- and then just copies the reference!
The result method to copy back into the array in a parallel fashion
Can extend to other data structures, need to choose an efficient intermediate data structure
Intermediate data strucures MUST be non-overlapping
4.3 Conc-tree Data Structure
Conc-tree a parallel counterpart of Cons List
Lists are built for sequential computations, trees can be done in parallel.
Trees are ONLY good for parallelism if they are balanced
Enter Conc trees! Contains level and size info at each node
An invariant -- the level cannot be greater than one
<> to Concat trees -- Concat making sure that the tree balance structure is maintained
Goes over cases of concating trees in various unbalanced situations.