[ANN] Faster Cappuccino framework by improving objj_msgSend implementation

260 views
Skip to first unread message

Martin Carlberg

unread,
Oct 6, 2015, 8:28:03 AM10/6/15
to objec...@googlegroups.com
Hi all!

I have made a more efficient implementation on the objj_msgSend function (and some more stuff) to make Objective-J code run faster.

I’m asking you to help me to find any bugs that are left. The return you get is an application that hopefully will run more than 20% faster.

Please check it out and let me know if it works for you. Detailed information and instructions are available at the below pull request.



Thanks,

- Martin

Aparajita

unread,
Oct 6, 2015, 10:41:28 AM10/6/15
to Cappuccino & Objective-J
Awesome! Could you briefly describe what changes were made in the implementation to make it faster?

Antoine Mercadal

unread,
Oct 6, 2015, 11:25:53 AM10/6/15
to objec...@googlegroups.com
Awesome, we'll use your branch in our master.

-- 
Antoine Mercadal
--
You received this message because you are subscribed to the Google Groups "Cappuccino & Objective-J" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectivej+...@googlegroups.com.
To post to this group, send email to objec...@googlegroups.com.
Visit this group at http://groups.google.com/group/objectivej.
For more options, visit https://groups.google.com/d/optout.

Martin Carlberg

unread,
Oct 6, 2015, 4:28:03 PM10/6/15
to objec...@googlegroups.com
Here is a more detailed description how this works.

1. More efficient objj_method implemention.
The old implementation was a javascript object with three properties  ’name’, ’types’ and ’method_imp’. The last one had the 
function that implemented the method. In Javascript a function is an object by itself. So the new implementation uses the
function as the object and adds two properties to it, ’name’ and ’types’. That bad thing here is that the ’name' property on
objj_method has to change to ’method_name' as it is used as a read only property for the name of the function. Now we
save a property lookup every time we need to get the method implementation.

2. objj_msgSend and objj_msgSendSuper functions has a more efficient implementation.
With a new objj_method implemention we save a property lookup as described above. objj_msgSendSuper was never
optimized and used the slow ’arguments’ attribute. Also it does not need to check if the class is initialized as it always is
if you do ’super’.

3. Array and Dictionary Literals generate more efficient code
The old implementation uses CPDictionary initWithObjectsAndKeys: that uses the slow ’arguments’ attribute. It now uses
initWithObjects:forKeys: that is much faster. The compiler also generated old objj_msgSend functions calls instead of the new
faster functions. It can inline as described below if that option is on.

4. The objj_msgSend and objj_msgSendSuper functions are optionally inlined by the compiler.
The old way the compiler was sending a message to an objects was by doing 'objj_msgSend(receiver, ”myMethod:”, myStuff)’.
This was earlier changed to a faster way by doing 'receiver.isa.objj_msgSend1(receiver, ”myMethod:”, myStuff)’ This was
faster as we had multiply send functions depending on how many arguments was passed to the function (the number at the
end of the function name). The slow ’arguments’ attribute could be avoided that way. Also a smart way of handling class initialization
speeded up the performance as we didn’t need to check that every time.
When inlining we get code that look like this: ’(receiver.isa.method_msgSend[”myMethod:”] || _objj_forward)(receiver, ”myMethod:”, myStuff)’
We now get the function that implements the method directly. If it does not exists the _objj_forward function will
be called with the same arguments. If the class is not initialized no method implementations exists so the _objj_forward function
will take care of the initialization and populate the method_msgSend attribute with all the method implementations.


I did some tests on loading a large nib file that took 2.50 seconds in Safari and 4.03 seconds in Chrome. With the new improvements
the time for Safari was down to 1.99 seconds and for Chrome it was 3.12 seconds. This is 20% faster for Safari and 23% for Chrome.
My tests also show that half of the improvement is from the bullets 1, 2 and 3 from above. The other half is from the inlining of the objj_msgSend
functions.


Happy coding,

- Martin


Antoine Mercadal

unread,
Oct 6, 2015, 4:33:11 PM10/6/15
to Cappuccino
I do feel the improvement of the cib loading. Not quantifiable, but it feels more responsive.

That’s awesome!

-- 
Antoine Mercadal



Todd Freese

unread,
Oct 6, 2015, 5:38:33 PM10/6/15
to Cappuccino & Objective-J
This is really awesome! Really great work!

Todd

Martin Carlberg

unread,
Oct 23, 2015, 6:18:56 AM10/23/15
to objec...@googlegroups.com
Hi All!

I have added two more pull request with nice performance increases (#2386 Faster theme state, #2389 Faster theme attribute).

I have focused on cib file loading. These 3 pull request makes cib loading twice as fast as before on all major browsers. You can also feel the speed increase in an overall snappier application.

Please try it out and help me find any outstanding bugs and in the same time I will promise you will never want to go back again. :-)


Best regards,

- Martin


Todd Freese

unread,
Oct 23, 2015, 4:11:46 PM10/23/15
to Cappuccino & Objective-J
This is awesome!

Any ETA on when it will get merged into master?

Todd

daboe01

unread,
Oct 24, 2015, 7:55:31 AM10/24/15
to Cappuccino & Objective-J
+1

Kjell Nilsson

unread,
Oct 24, 2015, 9:00:41 AM10/24/15
to objec...@googlegroups.com
+1

We are seeing vast improvment in our application. Really nice. And Safari rocks.

Regards
--kerusan


Martin Carlberg

unread,
Oct 26, 2015, 4:05:13 AM10/26/15
to objec...@googlegroups.com
Hi!

I think we should be able to get this into the master in a week if no issues are found.

- Martin

Martin Carlberg

unread,
Nov 1, 2015, 4:32:20 PM11/1/15
to objec...@googlegroups.com
Hi again!

This is now merged into master.

Faster theme state and theme attribute handling is also merge into master.

This gives us Nib file loading that is twice as fast as before and a general performance increase of everything in the Cappuccino frameworks.

Please read the how to use instructions in the pull request: https://github.com/cappuccino/cappuccino/pull/2384


Best regards,

- Martin

Chris Vaught

unread,
Nov 10, 2015, 8:55:02 AM11/10/15
to Cappuccino & Objective-J
I have updated to the latest master and I can no longer build my app. I followed the instructions on github to add the -O2 compiler flags but when I run jake release I get the following error:

Error on line 10805 of file [unknown]

RangeError: Maximum call stack size exceeded.


If I switch the -O2 back to just -O it compiles but the app freezes when I try to load the app. I also like to run jake debug. When doing so the resulting app compiles but freezes when trying to load.


Any help would be greatly appreciated.


Thanks

Chris

Martin Carlberg

unread,
Nov 10, 2015, 9:56:52 AM11/10/15
to objec...@googlegroups.com
Hi!

Have you tried ’jake clobber’ before building the new version of Cappuccino?

- Martin

Chris Vaught

unread,
Nov 10, 2015, 10:14:18 PM11/10/15
to Cappuccino & Objective-J
Yes.

Martin Carlberg

unread,
Nov 11, 2015, 5:49:58 AM11/11/15
to objec...@googlegroups.com
Ok, there are a number of things you can provide us with that will help in the investigation.

1. If you are getting 'Maximum call stack size exceeded’ at least Chrome will give you a stack trace. Please provide that to us as it might give us valuable information what is going on.
2. Have you tried to create a new application with ’capp gen MyApplication’. Will this small application work or does it also get errors or freezes?
3. When your application freezes do you get any messages in the Javascript console?
4. Can you run the Cappuccino test cases with ’jake test’ from command line?

- Martin

Chris Vaught

unread,
May 23, 2016, 9:45:43 PM5/23/16
to Cappuccino & Objective-J
So once I realized this issue was likely specific to my app I put off updating cappuccino for a super long time. I have finally updated and as of now I am once again regretting this tremendously. As mentioned previously, I have a functioning app that no longer works when updated to the latest master. When running jake release I get the following error:

Error on line 10805 of file [unknown]

RangeError: Maximum call stack size exceeded.


When I use jake debug the app compiles but the browser freezes when I try to load the app. I end up having to force quite the browser.


If anyone can help with this I would greatly appreciate. I am in quite a bind and really need to get this fixed right away. To answer your questions from many months ago:

1. If you are getting 'Maximum call stack size exceeded’ at least Chrome will give you a stack trace. Please provide that to us as it might give us valuable information what is going on.

I'm not sure how I do this in chrome to get a stack trace. The only time I get the max call stack exceeded error is when running jake release from within the command link.

2. Have you tried to create a new application with ’capp gen MyApplication’. Will this small application work or does it also get errors or freezes?

Yes. This works without any issue.

3. When your application freezes do you get any messages in the Javascript console?

No. None at all which is obviously quite frustrating.

4. Can you run the Cappuccino test cases with ’jake test’ from command line?

Jake test fails with the information shown below:

addFailure test=[ToolsTest testTools]: objj failed expected:<<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version = "1.0"><array><dict><key>line</key><integer>1</integer><key>sourcePath</key><string>/Users/chrisvaught/cappuccino/objjErrorTestFile.j</string><key>message</key><string>

@implementation AppController : CPObject{}@end

                                ^

ERROR line 1 in file:/Users/chrisvaught/cappuccino/objjErrorTestFile.j: Can&apos;t find superclass CPObject</string></dict></array></plist>

> but was:<<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version = "1.0"><array><dict><key>line</key><integer>9470</integer><key>sourcePath</key><string>/Users/chrisvaught/cappuccino/objjErrorTestFile.j</string><key>message</key><string>

@implementation AppController : CPObject{}@end

                                ^

ERROR line 1 in file:/Users/chrisvaught/cappuccino/objjErrorTestFile.j: Can&apos;t find superclass CPObject</string></dict></array></plist>

>


Trace not implemented


Test suite failed with 0 errors and 1 failures and 1298 successes

Total tests : 1299

Martin Carlberg

unread,
May 24, 2016, 5:00:32 AM5/24/16
to objec...@googlegroups.com
Hi Chris!

It is very hard to debug problems when running from the command line. It is much easier with running in a browser as they have modern debuggers that will help to find the problem.

You have one case when you got everything to compile but the app freezes the browser. I think this case is where we should start. You don’t provide much information about what is happening. You say the browser freezes and you have to force quit the browser. The console shows nothing. A few more questions:

1. Does the browser use any cpu while the app freezes?
2. What browsers have you tried it on?
3. Have you tried to log out messages with console.log(”XXX”) in different places to see how far it gets before it freezes. Line 1 in main.j is a good start as it will be executed just after all other classes has been loaded. Index-debug.html is also a good place if main.j does not log anything. Try line 1 in every source file you have as a last resort.
4. Can you run your app directly from source in the browser without doing ’jake debug’ before? This will compile the .j files as they are loaded into the browser.

Depending on what is happening you can try to remove parts of your code and figure out what is freezing your app.

You can also add a decorator that will log all objective-j message send calls. Add the below to you index-debug.html. It will log a trace for each method that is called. It will make you app very slow but you will see everything that is called. I use Chrome for this as it will print the log messages without any delay. Maybe Firefox does that too. Safari optimise for speed and console messages can be delayed.

            // DEBUG OPTIONS:

            var callLevel = "";
            objj_msgSend_decorate(function(msgSend) {
                return function(aReceiverOrSuper, aSelector)
                {
                    var aReceiver = aReceiverOrSuper && (aReceiverOrSuper.receiver || aReceiverOrSuper);

                    if (!aReceiver)
                        return msgSend.apply(this, arguments);

                    console.log(callLevel + (class_isMetaClass(aReceiver.isa) ? "+ [" : "- [") + aReceiver.isa.name + " " + aSelector + "]");

                    callLevel += "  ";
                    var result = msgSend.apply(this, arguments);
                    callLevel = callLevel.substring(0, callLevel.length - 2);

                    return result;
                };
            });

Good luck!

- Martin


Chris Vaught

unread,
May 24, 2016, 2:06:59 PM5/24/16
to Cappuccino & Objective-J
Thanks for the quick response.

1. Yes
2. Chrome and Safari
3. See below...
4. Yes

So I have added tons of log messages. main.j does log the message. I started adding log messages to my code and was able to find some specific spots in the code that causes everything to freeze but I haven't figured out the root cause. Let me try to describe a couple of issues:

In my AppController.j file I load a MainViewController.j. Within the MainVC's viewDidLoad method I then load multiple additional views. The first one that gets loaded is LibraryViewController. Code snippet is below.

    var libraryVC = [[LibraryViewController alloc] initWithCibName:@"LibraryView" bundle:[CPBundle mainBundle]];
    var theView = [libraryVC view];
    [leftUpperView addSubview:theView];
    [theView setFrame:[leftUpperView bounds]];

The last line where the frame is set causes the app to freeze. The view leftUpperView is a subview defined in the mainVC xib. This behavior seems quite odd to me but I wanted to continue testing so I just commented that last line out at which point the application continues on but then freezes again. Within the same method I load another view which happens to contain a CPSplitView and the app freezes again when I try to set the position of the split view:

    var bounds = [[self view] bounds];
    var width = CGRectGetWidth(bounds);
    [topSplitView setPosition:width-250 ofDividerAtIndex:0];

The variable topSplitView is defined in the xib and the width is greater than 250 so I'm not trying to set a negative position or anything. I then commented that last line out and then found another spot where the app freezes due to setting another splitView's position. I also commented it out at which point the entire MainViewController viewDidLoad method is finally able to complete. This then causes the app to freeze in the AppController.j applicationDidFinish method when setting the frame of mainVC to be equal to the full window. Code snippet below:

    var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask],
        contentView = [theWindow contentView],
        bounds = [contentView bounds];
    
    var mainViewController = [[MainViewController alloc] initWithCibName:@"MainView" bundle:[CPBundle mainBundle]],
        mainView = [mainViewController view];
    [mainView setFrame:bounds];
    
The last line once again causes the app to freeze. Using the debugger, the value of bounds is correct (equal to the size of the browser window).

Any idea what could be causing this very strange behavior?

Thanks
Chris

Martin Carlberg

unread,
May 24, 2016, 2:53:56 PM5/24/16
to objec...@googlegroups.com
Ok, I don’t know what this is but there are some things I would try:

1. Try to add the objj_msgSend_decorate call I showed earlier just before the line that freezes the app. This will log out all the method calls and might give a clue what is happening.
2. Take one or more cib files and try to do the same thing in a newly created project. It is much easier if you can reproduce it in a small project. If it is possible to reproduce try to remove views from the cib file to pin down where this is coming from.

- Martin

Chris Vaught

unread,
May 24, 2016, 4:08:33 PM5/24/16
to Cappuccino & Objective-J
1. This results in what appears to be an infinite loop of method calls. I'm not sure what to tell you other than it just constantly logs messages until eventually the browser freezes.
2. So far I haven't been able to reproduce the issue in a new app using the MainView cib. I will keep trying.

Chris Vaught

unread,
May 24, 2016, 5:04:49 PM5/24/16
to Cappuccino & Objective-J
Ok I think I have replicated the issue with a sample app. I have attached a zip containing a simple project.

AppController.j loads MainViewController.j which loads LibraryViewController.j which contains a CPOutlineView that I try to update the column DataView's to the call LibraryTableCell.j. Just as described previously the app freezes at the line below in the AppController.j file.

[mainView setFrame:bounds];

If you simply comment out the code contained in the viewDidLoad method of LibraryViewController.j then the app loads without issue.

The only difference between this app and my main app is that Chrome freezes but can still be closed without requiring a force quit. Also each class is the bar minimum to reproduce the error so there are lots of outlets that aren't connected since they don't exist in the class.
AdvancedHelloWorld2.zip

Martin Carlberg

unread,
May 25, 2016, 1:02:43 AM5/25/16
to objec...@googlegroups.com
Hi Chris,

It is quite simple :)

Your class LibraryTableCell in method ’setThemeState:' and ’unsetThemeState:' you will set the text color of the text field with your method ’setSelected:’. Now days almost all attributes are themed and the color of the text field is also themed. It is not good to alter a themed attribute inside ’setThemeState:' or ’unsetThemeState:'.

What you should do is to set the text color from somewhere else. Don’t override ’setThemeState:' and ’unsetThemeState:’ to set other attributes.


- Martin


<AdvancedHelloWorld2.zip>

Chris Vaught

unread,
May 25, 2016, 9:51:08 AM5/25/16
to Cappuccino & Objective-J
Thanks a ton Martin. I really appreciate the help. I don't think I would have ever found that issue.

My app now loads but when it is decoding JSON it is no longer correctly obtaining Boolean values for some reason. Consider a CPCoder object that contains some key's pointing to a Boolean value. The following line returns null:

var value = [coder decodeObjectForKey:"aValidKey"];

If I replace the line with the below code it works:

var value = coder._json.aValidKey;

Is there a reason decodeObjectForKey isn't working?

Keary Suska

unread,
May 25, 2016, 10:16:38 AM5/25/16
to objec...@googlegroups.com

> On May 25, 2016, at 7:51 AM, Chris Vaught <crva...@gmail.com> wrote:
>
> Thanks a ton Martin. I really appreciate the help. I don't think I would have ever found that issue.
>
> My app now loads but when it is decoding JSON it is no longer correctly obtaining Boolean values for some reason. Consider a CPCoder object that contains some key's pointing to a Boolean value. The following line returns null:
>
> var value = [coder decodeObjectForKey:"aValidKey"];
>
> If I replace the line with the below code it works:
>
> var value = coder._json.aValidKey;
>
> Is there a reason decodeObjectForKey isn't working?

Does -decodeBoolForKey: work as expected? It would be the canonical approach, and the toll-free bridging we get with numbers may not always work as expected in every case. Also if CPCoder is using JSON in its implementation there would have to be some trickery as JavaScript’s JSON.stringify can’t handle objj objects, so may rely on the correct object type being referenced.

HTH,

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

Chris Vaught

unread,
May 25, 2016, 10:32:28 AM5/25/16
to Cappuccino & Objective-J, cappu...@esoteritech.com
Thanks for the info. When I looked into your suggestion I realized that I was using CKJSONUnarchiver still which has a bug when determining the type of an object. If checked the type against Boolean instead of boolean.

Chris Vaught

unread,
May 25, 2016, 3:42:40 PM5/25/16
to Cappuccino & Objective-J
In my original inquiry I mentioned that I got an error when running jake release. Although the browser freezing issue is fixed and I am able to load my application when opening directly or using jake debug I am still getting the error when running jake release. Unfortunately the error message isn't particular useful (shown below again). Any suggestions on how to get this working correctly?

Error on line 10826 of file [unknown]

Chris Vaught

unread,
May 25, 2016, 10:26:20 PM5/25/16
to Cappuccino & Objective-J
I've fixed this issue by simply refactoring my largest couple of classes to reduce the class size. Seems like if the class is greater than a certain file size then the error below occurs.

Kerusan

unread,
May 26, 2016, 2:10:15 AM5/26/16
to objec...@googlegroups.com
Maximum call stack, is probably a circular method call chain.

Regards
Kerusan

Skickat från min iPhone
--
Reply all
Reply to author
Forward
0 new messages