Skip to content

Lucky Dozen

Today is my twelfth anniversary in Developer Tools at Apple!

Still love it, too!

Tagged

Raspberry Pi vs SPARCstation 20: Fight!

A couple weeks back, I tweeted the following:

Turns out a Raspberry Pi now is about 6× as fast as a SPARCstation 20 was 20 years ago. And a Pi 2 is more like 15× as fast.

I was a little low in my numbers, too — they’re more like 7× and 16× to 41× as fast — since I was going from memory!

Here’s how I came up with that.

The BYTE UNIX Benchmark

The standard benchmark for UNIX systems back in the day was the BYTE UNIX Benchmark, a set of benchmarks originally developed at a university and fleshed out substantially by BYTE Magazine so they could evaluate the new servers and workstations that were coming to market.

Even though BYTE itself is no more (RIP) the benchmark lives on: The most recent version was posted on Google Code and had some additional portability and enhancement work done. These days, the most up-to-date version is on GitHub.

What’s useful about this benchmark is that it’s scaled, and UNIX hasn’t changed all that much in itself, so it’s still moderately useful as a way to compare systems with each other.

I’d recently made an offhand comment that the Raspberry Pi, despite feeling “underpowered” by today’s standards, was actually extremely powerful — and that it put a decent workstation from the mid-1990s to shame, the kind of system we tended to be jealous of as college students.

What was a SPARCstation 20?

Back then, Sun was the biggest UNIX workstation vendor, primarily because both their hardware and baseline operating system were good and they offered a ton of flexibility in their product line.

In 1994, Sun introduced a new lineup of SPARCstation systems that had dramatically improved performance compared to their previous models — the original of which was so iconic that it defined the “pizza box” form factor for desktop workstations — and the SPARCstation 20 was one of their flagships.

Here are some specs for the Sun SPARCstation 20 model 61, which shipped in June 1994:

  • One 60 MHz SuperSPARC CPU
  • 1 MB of cache
  • 32MB RAM (expandable to 512MB)
  • 20 MB/second SCSI-2
  • 1152×900 8-bit graphics

In 1994, this was quite a substantial system, and it cost $16,195 in its minimum configuration. (That’s $25,580 today!) And if you used one, it felt like it: This thign was wicked fast.

This was also the last system for which the BYTE benchmark was re-indexed, defining this SPARCstation 20 to have a score of 10.0.

The Benchmarks

Actually running the benchmarks under Raspbian Jessie on my Raspberry Pi and Raspberry Pi 2 was trivial, literally just a matter of cloning the git repository and running the script.

Here are the results. Note that the Raspberry Pi 2 has two sets of results, because the BYTE UNIX Benchmark runs once to get “single-CPU” performance numbers and another time to get “multi-CPU” numbers. Its single-CPU numbers are really more like “single process” numbers, however, since the other three cores aren’t actually disabled while the benchmark is run.

System Benchmarks Index Values SS20-61 Result SS20-61 Index RPi Result RPi Index RPi2x1 Result RPi2x1 Index RPi2x4 Result RPi2x4 Index
Dhrystone 2 using register variables 16700.0 10.0 1647374.0 141.2 3000237.2 257.1 1948737.7 1023.9
Double-Precision Whetstone 55.0 10.0 239.6 43.6 435.3 79.1 1729.8 314.5
Execl Throughput 43.0 10.0 167.7 39.0 321.5 74.8 1210.6 281.5
File Copy 1024 bufsize 2000 maxblocks 3960.0 10.0 30363.8 76.7 70026.8 176.8 110940.6 280.2
File Copy 256 bufsize 500 maxblocks 1655.0 10.0 9473.6 57.2 20353.5 123.0 31384.0 189.6
File Copy 4096 bufsize 8000 maxblocks 5800.0 10.0 76219.4 131.4 186926.9 322.3 296346.9 510.9
Pipe Throughput 12440.0 10.0 118393.6 95.2 181562.5 146.0 713070.2 573.2
Pipe-based Context Switching 4000.0 10.0 14539.1 36.3 33809.8 84.5 126241.1 315.6
Process Creation 126.0 10.0 434.6 34.5 1190.8 94.5 2572.9 204.2
Shell Scripts (1 concurrent) 42.4 10.0 354.5 83.6 1087.0 256.4 2395.0 564.9
Shell Scripts (8 concurrent) 6.0 10.0 44.9 74.8 301.0 501.7 317.0 528.3
System Call Overhead 15000.0 10.0 276169.1 184.1 399939.7 266.6 1545514.4 1030.3
System Benchmarks Index Score 10.0 71.9 165.6 417.4

What does this tell us?

A lot can happen in 20 years. Even when it comes to things like I/O throughput, where the Raspberry Pi really falls down compared to other systems — because it attaches to everything via USB — it’s still way faster than a mid-1990s Sun that we all thought was extremely fast.

In particular, according to the indexes, a Raspberry Pi is about seven times as fast as a baseline SPARCstation 20 model 61 — and has substantially more RAM and storage, too. And the Raspberry Pi 2 is sixteen times as fast at single-threaded tasks, and on tasks where all cores can be put to use it’s forty one times faster.

Ideally, this would also mean that even a Raspberry Pi Zero should feel exceptionally fast. However, our software appetite has grown even faster than our appetite for fast hardware, and the feel of systems compared like this can demonstrate that well.

What’s next?

Well, I just got a DragonBoard 410c, which is a quad-core 64-bit ARM board using a Qualcomm CPU, and which doesn’t have any of the major design issues of the Raspberry Pi…

Tagged , , , ,

SBCL test failures on ARM

For hacking/prototyping/fun purposes I have a few embedded systems laying around. For example, I have a couple of Raspberry Pi systems, one of the original Raspberry Pi model B boards and one of the new Raspberry Pi 2 model B boards.

And on everything, I have the latest Steel Bank Common Lisp building.

On my Raspberry Pi, which is an armv6 device, I see the following failures in SBCL’s unit tests:

 Failure:            debug.impure.lisp / (TRACE ENCAPSULATE NIL)
 Failure:            debug.impure.lisp / (TRACE-RECURSIVE ENCAPSULATE NIL)
 Expected failure:   packages.impure.lisp / USE-PACKAGE-CONFLICT-SET
 Expected failure:   packages.impure.lisp / IMPORT-SINGLE-CONFLICT
 (62 tests skipped for this combination of platform and features)

On my Raspberry Pi 2, which is an armv7 device, I see the following additional failures:

 Failure:            float.pure.lisp / (SCALE-FLOAT-OVERFLOW BUG-372)
 Failure:            float.pure.lisp / (ADDITION-OVERFLOW BUG-372)
 Failure:            float.pure.lisp / (ADDITION-OVERFLOW BUG-372 TAKE-2)
 Failure:            debug.impure.lisp / (TRACE ENCAPSULATE NIL)
 Failure:            debug.impure.lisp / (TRACE-RECURSIVE ENCAPSULATE NIL)
 Expected failure:   packages.impure.lisp / USE-PACKAGE-CONFLICT-SET
 Expected failure:   packages.impure.lisp / IMPORT-SINGLE-CONFLICT
 (62 tests skipped for this combination of platform and features)

This says to me that, contrary to what some have told me, SBCL probably does need to distinguish the various ARM instruction set variants.

Is anyone actually working on SBCL on ARM?

I also have a DragonBoard 410c on the way, and it might be nice to have a fast Lisp on ARM64, though I suspect that’s a bit further out…

Tagged , , ,

Sad…

image1384260977.jpg

Cupertino is at one edge of the Santa Clara Valley, one of the best places on the continent to grow fruit.

This display is in our Whole Foods, one of the (if not the) largest stores they have. All of the brands are local and don’t exist any more, because we paved them over in favor of single-family homes and office parks and fucking lawns.

It’s really sad, and would have been easy to avoid, too, by building up instead of out. Instead, there are always new ballot measures in Cupertino to try to limit the “scale” of building – in other words, to prevent building up – and ensure the sprawl stays. And I expect that applies to the rest of the Santa Clara Valley as well, and a huge percentage of the United States as a whole, everywhere a “subdivision” has replaced farmland.

Even if we could build high-rise apartments and offices, it’s not certain that the land we’ve covered with little shitbox houses, chemically-maintained lawns, and asphalt could even be used for agriculture again. The land may well be “used up” and require extensive rehabilitation to even support parkland, much less farming.

Someday people will look back on this as a monumental disaster, an utter failure of urban planning ands demonstration of our society’s lack of any capacity for forethought. Probably once all of humanity is facing permanent food and water shortages, later this century.

Milestone

I’ve had my car for 10 years, and my odometer rolled over 100,000 miles earlier today.

Time to start thinking about a new car! Maybe a Tesla Model S, in a couple of years…

Tagged

When to use NSOperation vs. GCD

Mac OS X has a number of concurrency mechanisms, and that increases with Snow Leopard. In addition to run loops, threads (both Cocoa and POSIX) and operations, Snow Leopard adds Grand Central Dispatch (GCD), a very lightweight way to represent units of work and the style of concurrency they need, and have the system figure out how to schedule them.

But wait, don’t we have that already in NSOperation? It shouldn’t surprise you in the least to learn that NSOperation, on Snow Leopard, is built atop GCD. However, there are a number of differences between the two, and for that reason people have started to ask “How should I decide which to use when?”

The straightforward answer is a general guideline for all application development:

Always use the highest-level abstraction available to you, and drop down to lower-level abstractions when measurement shows that they are needed.

In this particular case, it means that when writing Cocoa applications, you should generally be using NSOperation rather than using GCD directly. Not because of a difference in efficiency, but because NSOperation provides a higher-level abstraction atop the mechanisms of GCD.

For example, you can set up a dependency between two NSOperations such that the second will only be run after the first is complete — even if they’re run on different queues. You can use KVO to observe the completion (or cancellation) of different operations — and you can create operations that support being cancelled in the first place. You can set a completion block to run after an application has finished. And you can, of course, create operations from blocks using NSBlockOperation.

You’ll also fit in better with Cocoa by using NSOperation in your high-level code. If you take a look at new Snow Leopard API on NSNotificationCenter, you’ll see one where you specify the NSOperationQueue on which you wish a notification to run a block.

Ultimately, you spend a little bit of overhead to use NSOperation instead of GCD, but you gain significant additional functionality that will be useful as you start to compose operations together. And that’s the biggest benefit of NSOperation: You can break up your application in terms of units of work that can not only be run on a queue, but also canceled, observed, and depended upon. This lets you easily define your data dependencies and ensure that you aren’t simply running code serially as a side-effect of locking.

Tagged , , , , , ,

Welcome to Snow Leopard!

Last week, Mac OS X 10.6 Snow Leopard was released!

Snow Leopard represents a lot of hard work by a lot of folks at Apple and at seeded third-party developers, and it really shows.

Now that it’s shipped, I can actually talk about some of the especially cool things this release has for developers.

Tagged , , , , ,

Rebutting Big Nerd Ranch on Objective-C 2.0 dot notation

The Big Nerd Ranch weblog has a new post about Objective-C 2.0 dot notation. They advocate never using it and they’re completely wrong.

Given my reaction on Twitter, several people have asked me to write a more in-depth rebuttal.

I’ve already addressed when and why you should use Objective-C 2.0 properties and dot notation in an earlier post, so I won’t go into that here. I’ll just repeat my response to their weblog.

Here’s what I wrote in response:

I disagree most emphatically. The whole point of dot notation is that, when combined with properties, it’s not just an alternative syntax for invoking methods. In fact, if that’s how you think about dot syntax, STOP. That’s not what it’s for at all.

What dot syntax and property declarations are for is separating object state from object behavior. Classical OOP only really defines objects as only exposing behavior but the past 30+ years have demonstrated rather aptly that objects consist of both. C# was actually pioneering in this; its concept of properties is rather similar to what the combination of property declarations and dot syntax enable in Objective-C.

To write idiomatic Objective-C 2.0 you should use @property to declare properties, and use dot syntax to access them. Period. Doing otherwise is a bad idea because it will create code that isn’t intention-revealing to other experienced Objective-C 2.0 developers. Teaching students to do otherwise is doing them a disservice, because you’re directly contradicting those responsible for the language and its evolution.

In short, Objective-C 2.0 has properties and dot notation as another way of expressing intent in your code. Use them for that, don’t refuse to use them just because they weren’t in earlier versions of the language, or because they require teaching another concept.

Tagged , , , , ,

Using “en” instead of “English” for your Xcode project’s development region

Various pieces of Mac OS X and iPhone documentation have said for quite a while that the “preferred” method is now to use ISO-639-1 (two-letter) or ISO-639-2 (three-letter) language codes codes for localization purposes. Out of the box, Xcode’s project templates still use “English” rather than “en” as their default localization.

How can you use the ISO-639 language codes everywhere in your project, rather than in just your non-English localizations?

It’s pretty straightforward, but it does require hand-editing of your Xcode project file. This means that before doing anything else, you must quit Xcode and Interface Builder.

The first step is to rename your existing localizable resource directories on disk from English.lproj to en.lproj. You can do it at the Terminal or in the Finder. If you’re using an SCM system such as Subversion, use it to do the renaming so it preserves your file history and such as well.

The next step is to adjust how your existing localizable resources are referenced in your Xcode project file. Open the project.pbxproj file inside your Xcode project in a plain text editor such as TextEdit (rather than Xcode) and replace all but one occurrences of the string English with the string en. The one you don’t want to replace is in a property under the PBXProject section named knownRegions: This is an array of localizations Xcode knows about. Just make sure en is at the end of the array:

knownRegions = (
    English,
    Japanese,
    French,
    German,
    en,
);

At this point, this should be the only place English appears in your project.pbxproj file.

Finally — and this is the important step, and non-discoverable step — you need to add a property to the project.pbxproj file to tell Xcode what the Development Region for your project is. Again, this gets put into the PBXProject section before the knownRegions key from above (Xcode will alphabetize the keys when saving your project for you); it should look like this:

developmentRegion = en;

The default value of this key within Xcode is English and Xcode won’t write the key into the project if the default isn’t changed. However, there’s no user interface within Xcode for actually changing this property, making it non-discoverable. Furthermore, if you are changing your project to use en as its default localization, and you don’t change this property, you won’t be able to add new localizations by inspecting your resources. (This is a known issue.)

At this point, you can save your modified project.pbxproj file and open your project again in Xcode. There’s one more thing you’ll have to change, this time in your product’s Info.plist file (or your products’ Info.plist files), before you can get back to work: You need to change the CFBundleDevelopmentRegion property to en rather than English.

Once you’ve made that change, you should safely be able to use your Xcode project normally, adding localized variants of your resources, building and running, and so on, and everything should Just Work — now using modern ISO language codes instead of the English language names!

Tagged , , ,

Unit testing Cocoa user interfaces: Use Check Methods

In the past, I’ve talked about ways to easily write unit tests for Cocoa applications, including tests for user interfaces using target-action and tests for interfaces using Cocoa bindings. There are some strategies you can apply to make writing tests for Cocoa code even easier, though. They’re just straightforward object-oriented programming, but sometimes we can forget that all the techniques you might use in your main code base can also be applied to our test code.

So here’s one trick that you can use in writing tests for Cocoa user interfaces, especially in ways that will make test-driven development easier.

Use a Common Base Class with Check Methods

The first, probably most important thing to do is use your own common base class for your tests. Don’t derive your tests directly from SenTestCase; instead, derive them from your own MyTestCase class that in turn derives from SenTestCase. This gives you a place to put all of the customization that’s appropriate for your project.

Sometimes you might need a series of assertions to verify some particular state. However, that series of assertions will be the same every time you need to verify that state. Or the assertions themselves aren’t very intention revealing so you always wind up putting a comment above them describing what they’re really doing.

Checking Target-Action

A simple example of this is checking a target-action connection in a user interface. Say you have a view controller that presents a collection of objects managed by an array controller. Its view has an Add button that should send -addObject: to the array controller. You might write a test for it like this:

- (void)testAddButtonSendsAddObjectToArrayController {
    STAssertEquals([viewController.addButton target], viewController.arrayController,
        @"The Add button should target the array controller.");
    STAssertEquals([viewController.addButton action], @selector(addObject:),
        @"The Add button should send the -addObject: action.");
}

That’s not too difficult to understand, but it could be made simpler — it could be done in a single assertion. You’d just write a method to check both the target and action at once and then use that method from your test, like this:

// in your test base class...

/*! Tells whether the control sends the action to the target. */
- (BOOL)checkControl:(NSControl *)control
         sendsAction:(SEL)action
            toTarget:(id)target
{
    return ([control action] == action)
        && ([control target] == target);
}

// in the tests specifying the view controller's behavior...

- (void)testAddButtonSendsAddObjectToArrayController {
    STAssertTrue([self checkControl:viewController.addButton
                        sendsAction:@selector(addObject:)
                           toTarget:viewController.arrayController],
        @"The Add button's action should send -addObject: to the array controller.");
}

That makes the intention behind the entire test a lot clearer, and it makes writing the test easier & safer since you can’t (say) forget to check either the target or the action.

It does lose a tiny bit of information: If the test fails, you’ll have to look at your xib file instead of the failure message to determine whether it’s because the target or the action isn’t set as you’ve specified. However, the trade-off in making the test easier to read and write is worth it here.

Checking Outlets

This is even worthwhile for single assertions, such as those you’d use to test that your outlets are connected in the first place. For example, you might initially write a test that your view controller is your table view’s delegate like this:

- (void)testViewControllerIsTableViewDelegate {
    STAssertEquals([viewController.tableView delegate], viewController,
        @"The table view's delegate should be the view controller.");
}

Rewriting it to be more intention-revealing with a simple check method would make it look like this:

// in your test base class...

/*! Tells whether the outlet is connected to the given destination. */
- (BOOL)checkOutlet:(id)outlet connectsTo:(id)destination {
    return outlet == destination;
}

// in the tests specifying the view controller's behavior...

- (void)testViewControllerIsTableViewDelegate {
    STAssertTrue([self checkOutlet:[viewController.tableView delegate]
                        connectsTo:viewController],
        @"The table view's delegate should be the view controller.");
}

You’re not saving any code by writing your test this way — you’re actually writing more — but its complexity has gone down because it requires less effort to see what it’s actually trying to do.

Checking Bindings

This is even worthwhile in situations where you may still need a few extra assertions. For example, Cocoa bindings are specified using a lot more information than just outlets and target-acton connections; you won’t always want to check (and specify the value of) all of it, but you can easily make the common parts clearer.

Going back to our Add button example, as is typical its enabled state should be bound to the array controller’s canAdd property. Writing a test to specify this involves using -infoForBinding: and interpreting the results, which takes a couple lines of code and a couple of assertions:

- (void)testAddButtonEnabledStateIsBoundToArrayControllerCanAdd {
    NSDictionary *bindingInfo = [viewController.addButton infoForBinding:NSEnabledBinding];
    STAssertNotNil(bindingInfo,
        @"The Add button's enabled state should be bound.");

    id observedObject = [bindingInfo objectForKey:NSObservedObjectKey];
    STAssertEquals(observedObject, viewController.arrayController,
        @"The Add button's enabled state should be bound to the array controller.");

    NSString *observedKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];
    STAssertEqualObjects(observedKeyPath, @"canAdd",
        @"The Add button's enabled state should be bound through the 'canAdd' key path.");
}

This isn’t too complicated, but it does start to get tedious, especially given that you have to remember to distinguish between STAssertEquals (pointer equality) and STAssertEqualObjects (object equivalence). Let’s put the tedium in one place:

/*! Tells whether the object's binding is connected through the given key path. */
- (BOOL)checkObject:(id)source
         hasBinding:(NSString *)binding
           toObject:(id)destination
            through:(NSString *)keyPath
{
    NSDictionary *bindingInfo = ;
    id observedObject = [bindingInfo objectForKey:NSObservedObjectKey];
    NSString *observedKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];

    return (bindingInfo != nil)
        && (observedObject == destination)
        && [keyPath isEqualToString:observedKeyPath];
}

// in the tests specifying the view controller's behavior...

- (void)testAddButtonEnabledStateIsBoundToArrayControllerCanAdd {
    STAssertTrue([self checkObject:viewController.addButton
                        hasBinding:NSEnabledBinding
                          toObject:viewController.arrayController
                           through:@"canAdd"],
        @"The Add button's enabled state should be bound to the array controller's 'canAdd' property.");
}

Much clearer!

Tagged , , , , , , , , , ,