Your SlideShare is downloading. ×
0
Java SE 7JUG SummerCamp 2011  La Rochelle, France
Fork / Join         Project Coin                     NIO.2              invokedynamic                        (...)
Fork / Join
java.lang.Thread     java.lang.Runnable     wait()<5   notify()     synchronized
Thread thread = new Thread() {   public void run() {     System.out.println(">>> Hello!");   }};thread.start();thread.join...
java.util.concurrent       executors       concurrent queues       concurrent collections       atomic variables5, 6   syn...
class Sum implements Callable<Long> {    private final long from;    private final long to;    Sum(long from, long to) {  ...
ExecutorService executor = Executors.newFixedThreadPool(2);List<Future<Long>> results = executor.invokeAll(asList(    new ...
1.0   Threads made easy1.11.21.31.4 5    Concurrency made easier 6 7    Parallelism made easier
Sum of an arrayn1   n2   n3   n4 n5   n6 n7     n8   n9     ...    ...     ...   ...     sum1                 sum2        ...
How many occurrences of“java.util.List” in this filesystem tree?
Load into RAM (1 thread)              Folder                                         Document subfolders: List<Folder>    ...
ForkJoinPool    Your workThread management      SplitMaximize parallelism   Fork subtasks      Work stealing    Join subta...
ForkJoinTask       join()                   join()                                              RecursiveAction           ...
16                          Folder word                         counting task 10                                          ...
(F/J demo)
Speedup&7"6"5"4"3"2"1"     2"   4"    6"   8"   10"   12"                                      #cores
No I/ONo synchronization / locksDecompose in simple recursive tasksDo not decompose below a thresholdTake advantage of mul...
try-with-resources
private void writeSomeData() throws IOException {  DataOutputStream out = new DataOutputStream(new FileOutputStream("data"...
private void writeSomeData() throws IOException {  DataOutputStream out = new DataOutputStream(new FileOutputStream("data"...
private void writeSomeData() throws IOException {  DataOutputStream out = null;  try {    out = new DataOutputStream(new F...
private void writeSomeData() throws IOException {  DataOutputStream out = null;  try {    out = new DataOutputStream(new F...
try (    FileOutputStream out = new FileOutputStream("output");    FileInputStream in1 = new FileInputStream(“input1”);   ...
public class AutoClose implements AutoCloseable {    @Override    public void close() {      System.out.println(">>> close...
AutoClose autoClose = new AutoClose();try {  autoClose.work();} finally {  autoClose.close();}
AutoClose autoClose = new AutoClose();try {  autoClose.work();} finally {  autoClose.close();}>>> work()   >>> close()   j...
AutoClose autoClose = new AutoClose();try {  autoClose.work();} finally {  autoClose.close();}            MyException     ...
“Caused by” ≠ “Also happened”
AutoClose autoClose = new AutoClose();MyException myException = null;try {  autoClose.work();} catch (MyException e) {  my...
AutoClose autoClose = new AutoClose();MyException myException = null;try {  autoClose.work();} catch (MyException e) {  my...
AutoClose autoClose = new AutoClose();MyException myException = null;try {  autoClose.work();} catch (MyException e) {  my...
AutoClose autoClose = new AutoClose();MyException myException = null;try {  autoClose.work();} catch (MyException e) {  my...
AutoClose autoClose = new AutoClose();MyException myException = null;try {  autoClose.work();} catch (MyException e) {  my...
try (AutoClose autoClose = new AutoClose()) {  autoClose.work();}
try (AutoClose autoClose = new AutoClose()) {        autoClose.work();      }>>> work()   >>> close()   MyException: Excep...
public void compress(String input, String output)             throws IOException {  try(    FileInputStream fin = new File...
public void compress(String input, String output) throws IOException {   public void compress(String paramString1, String ...
Not just syntactic sugarClutter-free, correct codeclose():   - be more specific than java.lang.Exception   - no exception i...
Diamond <>
List<String> strings = new LinkedList<Integer>();Map<String, List<String>> contacts =            new HashMap<Integer, Stri...
List<String> strings = new LinkedList<String>();strings.add("hello");strings.add("world");Map<String, List<String>> contac...
List<String> strings = new LinkedList<>();strings.add("hello");strings.add("world");Map<String, List<String>> contacts = n...
Map<String, String> map = new HashMap<String, String>() {   {     put("foo", "bar");     put("bar", "baz");   }};
Map<String, String> map = new HashMap<>() {   {     put("foo", "bar");     put("bar", "baz");   }};
Map<String, String> map = new HashMap<>() {   {     put("foo", "bar");     put("bar", "baz");   }};Diamond.java:43: error:...
class SomeClass<T extends Serializable & CharSequence> { }                                     Non-denotable type
class SomeClass<T extends Serializable & CharSequence> { }                                        Non-denotable typeSomeCl...
class SomeClass<T extends Serializable & CharSequence> { }                                        Non-denotable typeSomeCl...
Less ceremony when using genericsNo diamond with inner classes
Simplified varargs
private static <T> void doSomethingBad(List<T> list, T... values) {    values[0] = list.get(0);}public static void main(St...
This yields warnings + crash:private static <T> void doSomethingBad(List<T> list, T... values) {    values[0] = list.get(0...
private static <T> void doSomethingGood(List<T> list, T... values) {    for (T value : values) {        list.add(value);  ...
private static <T> void doSomethingGood(List<T> list, T... values) {    for (T value : values) {        list.add(value);  ...
@SuppressWarnings({“unchecked”,“varargs”})public static void main(String[] args) {    List<String> list = new LinkedList<>...
static, final methods, constructors@SafeVarargsprivate static <T> void doSomethingGood(List<T> list, T... values) {    for ...
Mark your code as safe for varargsYou can’t cheat with @SafeVaragsRemove @SuppressWarnings in client codeand pay attention...
Multi-catch & more precise rethrow
Class<?> stringClass = Class.forName("java.lang.String");Object instance = stringClass.newInstance();Method toStringMethod...
try {    Class<?> stringClass = Class.forName("java.lang.String");    Object instance = stringClass.newInstance();    Meth...
try {    Class<?> stringClass = Class.forName("java.lang.String");    Object instance = stringClass.newInstance();    Meth...
try {    Class<?> stringClass = Class.forName("java.lang.String");    Object instance = stringClass.newInstance();    Meth...
try {    Class<?> stringClass = Class.forName("java.lang.String");    Object instance = stringClass.newInstance();    Meth...
catch (SomeException e)Now implicitly final unless assigned...
static class SomeRootException extends Exception { }static class SomeChildException extends SomeRootException { }static cl...
static class SomeRootException extends Exception { }static class SomeChildException extends SomeRootException { }static cl...
Less clutterBe preciseDo not capture unintended exceptionsBetter exception flow analysis
Minor additions
// 123 in decimal, octal, hexadecimal and binarybyte decimal = 123;byte octal = 0_173;byte hexadecimal = 0x7b;byte binary ...
public static boolean isTrue(String str) {    switch(str.trim().toUpperCase()) {        case "OK":        case "YES":     ...
public static boolean isTrue(String s) {    String str = s.trim().toUpperCase();    int jump = -1;    switch(str.hashCode(...
Oracle Technology Network (more soon...)           Fork and Join: Java Can Excel at         Painless Parallel Programming ...
Julien Ponge                 @jponge  http://gplus.to/jpongehttp://julien.ponge.info/julien.ponge@insa-lyon.fr
Java 7 JUG Summer Camp
Upcoming SlideShare
Loading in...5
×

Java 7 JUG Summer Camp

1,835

Published on

Fork/Join and Project Coin.

Published in: Technology, News & Politics
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,835
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
27
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Transcript of "Java 7 JUG Summer Camp"

  1. 1. Java SE 7JUG SummerCamp 2011 La Rochelle, France
  2. 2. Fork / Join Project Coin NIO.2 invokedynamic (...)
  3. 3. Fork / Join
  4. 4. java.lang.Thread java.lang.Runnable wait()<5 notify() synchronized
  5. 5. Thread thread = new Thread() { public void run() { System.out.println(">>> Hello!"); }};thread.start();thread.join();
  6. 6. java.util.concurrent executors concurrent queues concurrent collections atomic variables5, 6 synchronization patterns rich locks
  7. 7. class Sum implements Callable<Long> { private final long from; private final long to; Sum(long from, long to) { this.from = from; this.to = to; } public Long call() { long acc = 0; for (long i = from; i <= to; i++) { acc = acc + i; } return acc; }}
  8. 8. ExecutorService executor = Executors.newFixedThreadPool(2);List<Future<Long>> results = executor.invokeAll(asList( new Sum(0, 10), new Sum(100, 1000), new Sum(10000, 1000000)));for (Future<Long> result : results) { System.out.println(result.get());}
  9. 9. 1.0 Threads made easy1.11.21.31.4 5 Concurrency made easier 6 7 Parallelism made easier
  10. 10. Sum of an arrayn1 n2 n3 n4 n5 n6 n7 n8 n9 ... ... ... ... sum1 sum2 sum3 sum1 + sum2 sum3 + (...) total sum
  11. 11. How many occurrences of“java.util.List” in this filesystem tree?
  12. 12. Load into RAM (1 thread) Folder Document subfolders: List<Folder> lines: List<String> documents: List<Document> Divide & conquer (#cores threads)
  13. 13. ForkJoinPool Your workThread management SplitMaximize parallelism Fork subtasks Work stealing Join subtasks Compose results
  14. 14. ForkJoinTask join() join() RecursiveAction fork() fork() vs RecursiveTaskChild ForkJoinTask Child ForkJoinTask
  15. 15. 16 Folder word counting task 10 5 1Document word Document word Folder word counting task counting task counting task 3 2 fork() Document word Document word n join() counting task counting task
  16. 16. (F/J demo)
  17. 17. Speedup&7"6"5"4"3"2"1" 2" 4" 6" 8" 10" 12" #cores
  18. 18. No I/ONo synchronization / locksDecompose in simple recursive tasksDo not decompose below a thresholdTake advantage of multicores with no painYou have more F/J candidate algorithms thanyou think!
  19. 19. try-with-resources
  20. 20. private void writeSomeData() throws IOException { DataOutputStream out = new DataOutputStream(new FileOutputStream("data")); out.writeInt(666); out.writeUTF("Hello"); out.close();}
  21. 21. private void writeSomeData() throws IOException { DataOutputStream out = new DataOutputStream(new FileOutputStream("data")); out.writeInt(666); out.writeUTF("Hello"); out.close();} what if...
  22. 22. private void writeSomeData() throws IOException { DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream("data")); out.writeInt(666); out.writeUTF("Hello"); } finally { if (out != null) { out.close(); } }}
  23. 23. private void writeSomeData() throws IOException { DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream("data")); out.writeInt(666); out.writeUTF("Hello"); } finally { if (out != null) { out.close(); } }} ...this is still far from correct!
  24. 24. try ( FileOutputStream out = new FileOutputStream("output"); FileInputStream in1 = new FileInputStream(“input1”); FileInputStream in2 = new FileInputStream(“input2”)) { // Do something useful with those 3 streams! // out, in1 and in2 will be closed in any case out.write(in1.read()); out.write(in2.read());}
  25. 25. public class AutoClose implements AutoCloseable { @Override public void close() { System.out.println(">>> close()"); throw new RuntimeException("Exception in close()"); } public void work() throws MyException { System.out.println(">>> work()"); throw new MyException("Exception in work()"); }}
  26. 26. AutoClose autoClose = new AutoClose();try { autoClose.work();} finally { autoClose.close();}
  27. 27. AutoClose autoClose = new AutoClose();try { autoClose.work();} finally { autoClose.close();}>>> work() >>> close() java.lang.RuntimeException: Exception in close()        at AutoClose.close(AutoClose.java:6)        at AutoClose.runWithMasking(AutoClose.java:19)        at AutoClose.main(AutoClose.java:52)
  28. 28. AutoClose autoClose = new AutoClose();try { autoClose.work();} finally { autoClose.close();} MyException m asked by Run>>> work() time Exception >>> close() java.lang.RuntimeException: Exception in close()        at AutoClose.close(AutoClose.java:6)        at AutoClose.runWithMasking(AutoClose.java:19)        at AutoClose.main(AutoClose.java:52)
  29. 29. “Caused by” ≠ “Also happened”
  30. 30. AutoClose autoClose = new AutoClose();MyException myException = null;try { autoClose.work();} catch (MyException e) { myException = e; throw e;} finally { if (myException != null) { try { autoClose.close(); } catch (Throwable t) { myException.addSuppressed(t); } } else { autoClose.close(); }}
  31. 31. AutoClose autoClose = new AutoClose();MyException myException = null;try { autoClose.work();} catch (MyException e) { myException = e; throw e;} finally { if (myException != null) { try { autoClose.close(); } catch (Throwable t) { myException.addSuppressed(t); } } else { autoClose.close(); }}
  32. 32. AutoClose autoClose = new AutoClose();MyException myException = null;try { autoClose.work();} catch (MyException e) { myException = e; throw e;} finally { if (myException != null) { try { autoClose.close(); } catch (Throwable t) { myException.addSuppressed(t); } } else { autoClose.close(); }}
  33. 33. AutoClose autoClose = new AutoClose();MyException myException = null;try { autoClose.work();} catch (MyException e) { myException = e; throw e;} finally { if (myException != null) { try { autoClose.close(); } catch (Throwable t) { myException.addSuppressed(t); } } else { autoClose.close(); }}
  34. 34. AutoClose autoClose = new AutoClose();MyException myException = null;try { autoClose.work();} catch (MyException e) { myException = e; throw e;} finally { if (myException != null) { try { autoClose.close(); } catch (Throwable t) { myException.addSuppressed(t); } } else { autoClose.close(); }}
  35. 35. try (AutoClose autoClose = new AutoClose()) { autoClose.work();}
  36. 36. try (AutoClose autoClose = new AutoClose()) { autoClose.work(); }>>> work() >>> close() MyException: Exception in work()        at AutoClose.work(AutoClose.java:11)        at AutoClose.main(AutoClose.java:16)        Suppressed: java.lang.RuntimeException: Exception in close()               at AutoClose.close(AutoClose.java:6)               at AutoClose.main(AutoClose.java:17)
  37. 37. public void compress(String input, String output) throws IOException { try( FileInputStream fin = new FileInputStream(input); FileOutputStream fout = new FileOutputStream(output); GZIPOutputStream out = new GZIPOutputStream(fout) ) { byte[] buffer = new byte[4096]; int nread = 0; while ((nread = fin.read(buffer)) != -1) { out.write(buffer, 0, nread); } }}
  38. 38. public void compress(String input, String output) throws IOException { public void compress(String paramString1, String paramString2) try(         throws IOException { FileInputStream fin = new FileInputStream(input);     FileInputStream localFileInputStream = new FileInputStream(paramString1); FileOutputStream fout = new FileOutputStream(output);     Object localObject1 = null; GZIPOutputStream out = new GZIPOutputStream(fout)     try { ) {         FileOutputStream localFileOutputStream = new FileOutputStream(paramString2); byte[] buffer = new byte[4096];         Object localObject2 = null; int nread = 0;         try { while ((nread = fin.read(buffer)) != -1) {             GZIPOutputStream localGZIPOutputStream = new GZIPOutputStream(localFileOutputStream); out.write(buffer, 0, nread);             Object localObject3 = null; }             try { }                 byte[] arrayOfByte = new byte[4096];}                 int i = 0;                 while ((i = localFileInputStream.read(arrayOfByte)) != -1) {                     localGZIPOutputStream.write(arrayOfByte, 0, i);                 }             } catch (Throwable localThrowable6) {                 localObject3 = localThrowable6;                 throw localThrowable6;             } finally {                 if (localGZIPOutputStream != null) {                     if (localObject3 != null) {                         try {                             localGZIPOutputStream.close();                         } catch (Throwable localThrowable7) {                             localObject3.addSuppressed(localThrowable7);                         }                     } else {                         localGZIPOutputStream.close();                     }                 }             }         } catch (Throwable localThrowable4) {             localObject2 = localThrowable4;             throw localThrowable4;         } finally {             if (localFileOutputStream != null) {                 if (localObject2 != null) {                     try {                         localFileOutputStream.close();                     } catch (Throwable localThrowable8) {                         localObject2.addSuppressed(localThrowable8);                     }                 } else {                     localFileOutputStream.close();                 }             }         }     } catch (Throwable localThrowable2) {         localObject1 = localThrowable2;         throw localThrowable2;     } finally {         if (localFileInputStream != null) {             if (localObject1 != null) {                 try {                     localFileInputStream.close();                 } catch (Throwable localThrowable9) {                     localObject1.addSuppressed(localThrowable9);                 }             } else {                 localFileInputStream.close();             }         }     } }
  39. 39. Not just syntactic sugarClutter-free, correct codeclose(): - be more specific than java.lang.Exception - no exception if it can’t fail - no exception that shall not be suppressed (e.g., java.lang.InterruptedException)
  40. 40. Diamond <>
  41. 41. List<String> strings = new LinkedList<Integer>();Map<String, List<String>> contacts = new HashMap<Integer, String>();
  42. 42. List<String> strings = new LinkedList<String>();strings.add("hello");strings.add("world");Map<String, List<String>> contacts = new HashMap<String, List<String>>();contacts.put("Julien", new LinkedList<String>());contacts.get("Julien").addAll(asList("Foo", "Bar", "Baz"));
  43. 43. List<String> strings = new LinkedList<>();strings.add("hello");strings.add("world");Map<String, List<String>> contacts = new HashMap<>();contacts.put("Julien", new LinkedList<String>());contacts.get("Julien").addAll(asList("Foo", "Bar", "Baz"));
  44. 44. Map<String, String> map = new HashMap<String, String>() { { put("foo", "bar"); put("bar", "baz"); }};
  45. 45. Map<String, String> map = new HashMap<>() { { put("foo", "bar"); put("bar", "baz"); }};
  46. 46. Map<String, String> map = new HashMap<>() { { put("foo", "bar"); put("bar", "baz"); }};Diamond.java:43: error: cannot infer type arguments for HashMap; Map<String, String> map = new HashMap<>() { ^ reason: cannot use <> with anonymous inner classes1 error
  47. 47. class SomeClass<T extends Serializable & CharSequence> { } Non-denotable type
  48. 48. class SomeClass<T extends Serializable & CharSequence> { } Non-denotable typeSomeClass<?> foo = new SomeClass<String>();SomeClass<?> fooInner = new SomeClass<String>() { };SomeClass<?> bar = new SomeClass<>();SomeClass<?> bar = new SomeClass<>() { };
  49. 49. class SomeClass<T extends Serializable & CharSequence> { } Non-denotable typeSomeClass<?> foo = new SomeClass<String>();SomeClass<?> fooInner = new SomeClass<String>() { };SomeClass<?> bar = new SomeClass<>();SomeClass<?> bar = new SomeClass<>() { }; No denotable type to generate a class
  50. 50. Less ceremony when using genericsNo diamond with inner classes
  51. 51. Simplified varargs
  52. 52. private static <T> void doSomethingBad(List<T> list, T... values) { values[0] = list.get(0);}public static void main(String[] args) { List list = Arrays.asList("foo", "bar", "baz"); doSomethingBad(list, 1, 2, 3);}
  53. 53. This yields warnings + crash:private static <T> void doSomethingBad(List<T> list, T... values) { values[0] = list.get(0);}public static void main(String[] args) { List list = Arrays.asList("foo", "bar", "baz"); doSomethingBad(list, 1, 2, 3);} Heap Pollution: List = List<String>
  54. 54. private static <T> void doSomethingGood(List<T> list, T... values) { for (T value : values) { list.add(value); }}
  55. 55. private static <T> void doSomethingGood(List<T> list, T... values) { for (T value : values) { list.add(value); }}$ javac Good.javaNote: Good.java uses unchecked or unsafe operations.Note: Recompile with -Xlint:unchecked for details.
  56. 56. @SuppressWarnings({“unchecked”,“varargs”})public static void main(String[] args) { List<String> list = new LinkedList<>(); doSomethingGood(list, "hi", "there", "!");}$ javac Good.java$
  57. 57. static, final methods, constructors@SafeVarargsprivate static <T> void doSomethingGood(List<T> list, T... values) { for (T value : values) { list.add(value); }}
  58. 58. Mark your code as safe for varargsYou can’t cheat with @SafeVaragsRemove @SuppressWarnings in client codeand pay attention to real warnings
  59. 59. Multi-catch & more precise rethrow
  60. 60. Class<?> stringClass = Class.forName("java.lang.String");Object instance = stringClass.newInstance();Method toStringMethod = stringClass.getMethod("toString");System.out.println(toStringMethod.invoke(instance));
  61. 61. try { Class<?> stringClass = Class.forName("java.lang.String"); Object instance = stringClass.newInstance(); Method toStringMethod = stringClass.getMethod("toString"); System.out.println(toStringMethod.invoke(instance));} catch (ClassNotFoundException e) { e.printStackTrace();} catch (InstantiationException e) { e.printStackTrace();} catch (IllegalAccessException e) { e.printStackTrace();} catch (NoSuchMethodException e) { e.printStackTrace();} catch (InvocationTargetException e) { e.printStackTrace();}
  62. 62. try { Class<?> stringClass = Class.forName("java.lang.String"); Object instance = stringClass.newInstance(); Method toStringMethod = stringClass.getMethod("toString"); System.out.println(toStringMethod.invoke(instance));} catch (Throwable t) { t.printStackTrace();}
  63. 63. try { Class<?> stringClass = Class.forName("java.lang.String"); Object instance = stringClass.newInstance(); Method toStringMethod = stringClass.getMethod("toString"); System.out.println(toStringMethod.invoke(instance));} catch (Throwable t) { t.printStackTrace();} How about SecurityException?
  64. 64. try { Class<?> stringClass = Class.forName("java.lang.String"); Object instance = stringClass.newInstance(); Method toStringMethod = stringClass.getMethod("toString"); System.out.println(toStringMethod.invoke(instance));} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace();} Union of alternatives
  65. 65. catch (SomeException e)Now implicitly final unless assigned...
  66. 66. static class SomeRootException extends Exception { }static class SomeChildException extends SomeRootException { }static class SomeOtherChildException extends SomeRootException { }public static void main(String... args) throws Throwable {try { throw new SomeChildException();} catch (SomeRootException firstException) { try { throw firstException; } catch (SomeOtherChildException secondException) { System.out.println("I got you!"); }}
  67. 67. static class SomeRootException extends Exception { }static class SomeChildException extends SomeRootException { }static class SomeOtherChildException extends SomeRootException { }public static void main(String... args) throws Throwable {try { throw new SomeChildException();} catch (SomeRootException firstException) { try { throw firstException; } catch (SomeOtherChildException secondException) { System.out.println("I got you!"); }}$ javac Imprecise.javaImprecise.java:13: error: exception SomeOtherChildException is never thrown in body ofcorresponding try statement } catch (SomeOtherChildException secondException) { ^1 error
  68. 68. Less clutterBe preciseDo not capture unintended exceptionsBetter exception flow analysis
  69. 69. Minor additions
  70. 70. // 123 in decimal, octal, hexadecimal and binarybyte decimal = 123;byte octal = 0_173;byte hexadecimal = 0x7b;byte binary = 0b0111_1011;// Other valuesdouble doubleValue = 1.111_222_444F;long longValue = 1_234_567_898L;long longHexa = 0x1234_3b3b_0123_cdefL;
  71. 71. public static boolean isTrue(String str) { switch(str.trim().toUpperCase()) { case "OK": case "YES": case "TRUE": return true; case "KO": case "NO": case "FALSE": return false; default: throw new IllegalArgumentException("Not a valid true/false string."); }}
  72. 72. public static boolean isTrue(String s) { String str = s.trim().toUpperCase(); int jump = -1; switch(str.hashCode()) { case 2404: if (str.equals("KO")) { jump = 3; Bucket } break;(...) switch(jump) {(...) case 3: case 4: case 5: return false; default: Real code throw new IllegalArgumentException( "Not a valid true/false string."); }}
  73. 73. Oracle Technology Network (more soon...) Fork and Join: Java Can Excel at Painless Parallel Programming Too! http://goo.gl/tostz Better Resource Management with Java SE 7: Beyond Syntactic Sugar http://goo.gl/7ybgr
  74. 74. Julien Ponge @jponge http://gplus.to/jpongehttp://julien.ponge.info/julien.ponge@insa-lyon.fr
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×