Presentation is loading. Please wait.

Presentation is loading. Please wait.

Multi-Dispatch in the Java Virtual Machine TM What is (dynamic) multi-dispatch? Method selection based on the types of … all (or more than one) of the.

Similar presentations


Presentation on theme: "Multi-Dispatch in the Java Virtual Machine TM What is (dynamic) multi-dispatch? Method selection based on the types of … all (or more than one) of the."— Presentation transcript:

1 Multi-Dispatch in the Java Virtual Machine TM What is (dynamic) multi-dispatch? Method selection based on the types of … all (or more than one) of the arguments … at execution time (not compile time). Theatre t = new Theatre(); Orchestra o = new Orchestra(); Person duane = new Person(" Duane "); Person chris = new Student(" Chris "); System.out.println(duane + ": " + t.admission(duane) + " and " + o.admission(duane) + "." ); System.out.println(chris + ": " + t.admission(chris) + " and " + o.admission(chris) + "." ); Duane: pays $10 and pays $40. Chris: pays $10 and pays $40. Duane: pays $10 and pays $40. Chris: pays $5 and pays $25. class Person { /* … */ } class Student extends Person { /* … */ } class Theatre { String admission(Person p) {return "pays $10" ;} String admission(Student s) {return "pays $5" ; }} class Orchestra { String admission(Person p) {return "pays $40" ;} String admission(Student s) {return "pays $25" ;}} What does this code return? Given these definitions: Isn’t this method overloading? Statically, chris is a ‘Person’. Dynamically, chris is a ‘Student’. The Container Problem still applies; static multi-dispatch (method overloading) isn’t enough, nor are parametric types: Person lineUp[] = { new Student(" Chris "), new Person(" Paul "), new Person(" Duane "), new Student(" Steve "), new Person(" Wade ")}; for (int i=0; i<lineUp.length; i++) { System.out.println(lineUp[i] + ": " + t.admission(lineUp[i]) + " and " + o.admission(lineUp[i]) + "." ); } Method overloading requires the compiler to know the type of lineUp[i]. The compiler sees them all as type ‘Person’; the container obscures the dynamic type of ‘Student’ elements. The programmer cannot simply insert a cast to ‘Student’ – some elements are ‘Person’. Previously, how did people work around this problem? Type Numbering abstract class Venue { abstract String studentAdmission(); abstract String personAdmission(); String admission(Person p) { if (p instanceof Student) {return studentAdmission();} else if (p instanceof Person){return personAdmission();} }} class Theatre extends Venue { String personAdmission() {return "pays $10" ;} String studentAdmission() {return "pays $5" ; }} class Orchestra extends Venue { String personAdmission() {return "pays $40" ;} String studentAdmission() {return "pays $25" ;}} Difficulties:  overhead: second method invocation  maintainability: custom dispatch code  clarity: logic spread across all classes Visitor Pattern (Double Dispatch) abstract class Visitor { abstract String visitTheatre(Theatre t); abstract String visitOrchestra(Orchestra o); } class Person extends Visitor { String visitTheatre(Theatre t) {return "pays $10" ;} String visitOrchestra(Orchestra o){return "pays $40" ;}} class Student extends Person { String visitTheatre(Theatre t) {return "pays $5" ;} String visitOrchestra(Orchestra o){return "pays $25" ;}} abstract class Venue { abstract String admission(Visitor v); } class Theatre extends Venue { String admission(Visitor v){return v.visitTheatre(this); }} class Orchestra extends Venue { String admission(Visitor v) {return v.visitOrchestra(this);}} Type Testing class Person { static final int PERSON_TYPE = 0; protected int id = PERSON_TYPE; } class Student extends Person{ static final int STUDENT_TYPE = 1; Student() { this.id = STUDENT_TYPE }; } class Theatre { String admission(Person p) { switch (p.id) { case PERSON_TYPE: return "pays $10" ; case STUDENT_TYPE: return "pays $5" ; }} class Orchestra { String admission(Person p) { switch (p.id) { case PERSON_TYPE: return "pays $40" ; case STUDENT_TYPE: return "pays $25" ; }} Difficulties:  overhead: second method invocation  overhead: instanceof is costly  error-prone: always test subtypes first  Type Testing that trades extensibility and maintainability of OO for performance: replicated switch  second method invocation instanceof  manifest constants But, how common is this problem? Binary Operations Drag and Drop This occurs frequently enough that a solution (Visitor) was included in the original GOF design patterns; common situations include: Event Programming class AWTEvent { /* … */ } class MouseEvent extends AWTEvent { /* … */ } class KeyboardEvent extends AWTEvent { /* … */ } class FocusEvent extends AWTEvent { /* … */ } … class Component { void processEvent(AWTEvent e) { /* … */ } } class Window extends Component { /* … */ } class Button extends Component { /* … */ } class ScrollBar extends Component { /* … */ } class JComponent extends Component { /* … */ } … class Viewer { DropTargetListener vDropListener; …} class Printer {DropTargetListener pDropListener; …} class TextDocument implements Transferable {/* … */ } class Spreadsheet implements Transferable {/* … */ } class GraphicsImage implements Transferable {/* … */ } The heart of AWT (and Swing) is dispatching a queue of various kinds of events to a variety of GUI components. Drag and drop is the prototypical situation requiring dispatch on multiple arguments. The actions of the drop target depend on the kind of target and the kind of object transferred. Many operations, such as merges and comparisons, depend intimately on the types of their operands. class CustomerList { CustomerList merge(CustomerList c) { //O(nlogn) return this.sort().merge(c.sort()); } CustomerList merge(SortedCustomerList s) { //O(nlogn) return this.sort().merge(s); } } class SortedCustomerList extends CustomerList { CustomerList merge(CustomerList c) { //O(nlogn) return this.merge(c.sort()); } CustomerList merge(SortedCustomerList s) { //O(n) /* actually merge in linear time */ } } How does the JVM perform a multi-dispatch? Native multi-dispatch in the Java Virtual Machine At a method invocation, the JVM has the message selector the arguments on a stack the list of potential methods … Theatre t = new Theatre(); Orchestra o = new Orchestra(); Person duane = new Person(" Duane "); Person chris = new Student(" Chris "); System.out.println(duane + ": " + t.admission(duane) + " and " + o.admission(duane) + "." ); System.out.println(chris + ": " + t.admission(chris) + " and " + o.admission(chris) + "." ); Behavior admission Theatre, Person  String Theatre, Student  String Orchestra, Person  String Orchestra, Student  String [ ^"pays $10" ] [ ^"pays $5" ] [ ^"pays $25" ] [ ^"pays $40" ] … // bytecode before t.admission(chris) aload 1 // push t aload 2 // push chris invokevirtual #6 // method Theatre.admission(Person)String … // bytecode after t.admission(chris) Student chris Theatre t … byte code stackmethod dictionary … so the JVM can determine the types of the stacked arguments select another method that more closely matches the argument types What does the multi-dispatch version look like? class Person { /* … */ } class Student extends Person { /* … */ } class Theatre implements VirtualMultiDispatchable { String admission(Person p) {return "pays $10" ;} String admission(Student s) {return "pays $5" ; }} class Orchestra implements VirtualMultiDispatchable { String admission(Person p) {return "pays $40" ;} String admission(Student s) {return "pays $25" ;}} Without language extensions or custom dispatch code, the programmer can target multi-dispatch to only the classes that require this technique. Theatre t = new Theatre(); Orchestra o = new Orchestra(); Person duane = new Person(" Duane "); Person chris = new Student(" Chris "); System.out.println(duane + ": " + t.admission(duane) + " and " + o.admission(duane) + "." ); System.out.println(chris + ": " + t.admission(chris) + " and " + o.admission(chris) + "." ); Duane: pays $10 and pays $40. Chris: pays $5 and pays $25. Implement empty marker interface (like Cloneable ) signaling JVM to multi-dispatch this class How efficient is it? High-performance SRP+ multi-dispatcher operates faster than double- dispatching in the Java interpreter (tested on Sun classic VM for Linux): Multi-Dispatch AWT and Swing Advantages:  No second method invocation  No custom written dispatch code  No type tests  No type numbering  Extensible without changing existing libraries We modified Swing and AWT to use multi-dispatch:  changed 92 out of 846 classes  removed 123 custom-coded dispatcher methods  eliminated 171 decision points (if/else and switch/case statements)  reduced average decisions in each changed method from 3.8 to 2.0  replaced 4.74 million dispatches with 2.35 million multi-dispatches … with faster dispatch What are the benefits of this multi-dispatch approach? No language extensions or preprocessors:  maintains tool support: debuggers, profilers work on as-written code  Java source and binary compatible: we compile with the original javac  extend third-party components to use multi-dispatch without source  maintains reflection API – methods appear exactly as written Programmer-targeted multi-dispatch: use it only where needed  single dispatch has unchanged performance and semantics Full JVM support:  multi-dispatch instance and static methods (separately selectable)  multi-dispatch native and private methods, constructors, and super calls  add multi-dispatch to languages that emit.class files (Eiffel, Ada) Better performance than custom type-numbers without sacrificing OO Christopher DutchynPaul LuDuane SzafronSteve BromlingWade Holst Is this still OO?


Download ppt "Multi-Dispatch in the Java Virtual Machine TM What is (dynamic) multi-dispatch? Method selection based on the types of … all (or more than one) of the."

Similar presentations


Ads by Google