1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
|
#author Matthias Benkard
#date 2007-09-23 - <lisp>(format-time-string "%Y-%m-%d")</lisp>
#title Objective-CL Development Diary
#desc Random news from the Objective-CL lab
; Time-stamp: <2008-03-23 12:56:15 mulk>
;
; C-c i t insert-time-stamp
; C-c C-t muse-project-publish-this-file
; C-c C-e muse-edit-link-at-point
; C-c C-i muse-insert-thing
----
Context: [[http://matthias.benkard.de/objective-cl][The Objective-CL Project]].
----
* 2008-03-21, 21:17:36 CET
** A Self-Evaluating Form
**#1=(quote #1#)**
<example>
+---------------+
| |
v |
+------+------+ |
| | | |
| | --------+
| | | |
+--|---+------+
|
v
QUOTE
</example>
* 2008-03-21, 19:50:17 CET
** SUPER vs. CALL-NEXT-METHOD
It just dawned on me that by changing the way
**objective-c-generic-function** dispatch works, it may be possible to give
**call-next-method** a useful semantics.
Assuming =MLKFnord= is a Lisp-backed Objective-C class which =mulk= is an
instance of, and =MLKSlack= is a Lisp-backed superclass of =MLKFnord=, then
the call =[mulk foo: 10 bar: nil]= is done as follows:
1. =MLKFnord's= =foo=:bar: callback is entered.
2. The callback calls (#/foo:bar: 'mlk-fnord mulk 10 nil).
3. The generic function computes an effective method for these
arguments and calls it.
4. The effective method calls **super**.
5. **super** causes =MLKSlack's= =foo:bar:= callback to be entered.
6. The callback calls =(#/foo:bar: 'mlk-slack mulk 10 nil)=. And so on.
I'm experimenting with an alternative approach that does away with the
extra class name argument. Here are some random thoughts (which may be
completely wrong), in no particular order:
* For **call-next-method** to be useful in a multiple inheritance setting,
Objective-C classes must be last in the class precedence list,
because =NSObject's= version of a method will never call **super** and
therefore the call chain will always end there, regardless of what we
do on the Lisp side.
* A method's definition class must be stored in its method metaobject.
* A callback needs to call **compute-applicable-methods**, remove all
methods up to the "current" one (which is the first one with the
callback's class as the definition class, that is, the class named by
what is now the artificial class name argument that makes **super** calls
work), and call the form returned by **compute-effective-method** for the
trimmed list of applicable methods.
The nice thing is that this gives **call-next-method** a meaning that isn't
completely bogus. The bad thing is that it might make method calls
(**call-next-method** and **super**, at least) slow. This new idea is, after
all, way off the usual way of invoking the CLOS dispatch mechanism.
Note that we could still use normal CLOS generic function dispatch if
the object's class is the same as the callback's definition class.
* 2008-03-19, 12:38:56 CET
** Evil Microoptimisation
After moving **(object-get-class receiver)** from **invoke-by-name-super-v** to
**retrieve-method-signature-info**:
<example>
Evaluation took:
3.764 seconds of real time
3.760235 seconds of user run time
0.012001 seconds of system run time
[Run times include 0.096 seconds GC run time.]
0 calls to %EVAL
0 page faults and
72,809,568 bytes consed.
</example>
Finally, an excuse to use the **&aux** lambda list keyword. Hooray!
* 2008-03-19, 03:03:50 CET
** Optimising INVOKE
The benchmark:
<example>
(let ((x (invoke (find-objc-class 'ns-method-signature)
:method-signature-for-selector 'new)))
(time (dotimes (i 100000) (invoke x :get-argument-type-at-index 0))))
</example>
Before:
<example>
Evaluation took:
7.727 seconds of real time
7.136446 seconds of user run time
0.080005 seconds of system run time
[Run times include 0.288 seconds GC run time.]
0 calls to %EVAL
0 page faults and
218,448,224 bytes consed.
</example>
After:
<example>
Evaluation took:
5.868 seconds of real time
5.824364 seconds of user run time
0.032002 seconds of system run time
[Run times include 0.256 seconds GC run time.]
0 calls to %EVAL
0 page faults and
122,487,656 bytes consed.
</example>
What I did was add a **name** slot to class **selector** so that **selector-name**
need only access a slot now instead of calling a foreign function and
converting the returned value to a Lisp string.
After that, I enhanced **intern-pointer-wrapper** to intern classes, because
**object-get-class**, another frequently called function, had to first
acquire the class name associated with a class pointer and then finally
call **find-objc-class-by-name** with that name. The result:
<example>
Evaluation took:
4.058 seconds of real time
4.020251 seconds of user run time
0.016001 seconds of system run time
[Run times include 0.148 seconds GC run time.]
0 calls to %EVAL
0 page faults and
76,830,440 bytes consed.
</example>
That's gonna be it for now. I'll call it a night.
(By the way, this is using CFFI 0.9.2. The Darcs version is
significantly slower. Remember the CFFI speed hack I mentioned sometime
way back in the beginning? It's needed for a Darcs CFFI, but I don't
think it's needed for CFFI 0.9.2.)
* 2008-03-18, 15:31:30 CET
** Profiling INVOKE
I always profile **invoke** like this:
<example>
(let ((x (invoke (find-objc-class 'ns-method-signature)
:method-signature-for-selector 'new)))
(sb-sprof:with-profiling (:max-samples 500
:loop t
:report :flat)
(dotimes (i 100) (invoke x :get-argument-type-at-index 0))))
</example>
Of course, this is a ridiculous microbenchmark that doesn't yield much
information about actual Objective-C usage, but it's certainly useful
for finding the bottlenecks of **invoke** calls.
Whether the performance of repeated invocation of the very same method
on the very same object is all that interesting is, of course, an
entirely different matter.
* 2008-03-07, 02:17:50 CET
** Memory Management
This is what the GNUstep manual says about the =#dealloc= method:
In some circumstances, an object may wish to prevent itself from being
deallocated, it can do this simply [by] refraining from calling the
superclass implementation.
Maybe we could use this in the case of a garbage-collected runtime.
* 2008-03-05, 18:48:48 CET
** CMUCL and the MOP
I have just discovered a discouraging bug in CMUCL's version of PCL that
is confirmed by what Closer-to-MOP's feature list says:
**pcl:set-funcallable-instance-function** does not accept a closure as its
second argument, it only accepts pure functions that don't close over
things.
Really. That is a horrible bug. It means that Objective-CL is broken
on CMUCL in a fundamental way (see the definition of **shared-initialize
:after (selector ...)** in =data-types.lisp=) and I don't have the slightest
idea how to work around it.
* 2008-02-24, 22:39:03 CET
** PCL Problems
Collecting all Objective-C classes and creating CLOS classes out of
them, which is what the only recently introduced function
**collect-classes** does, works reliably both on Allegro CL and on GNU
CLISP. There are some serious problems on CMUCL and SBCL, though.
First, some stats (this is on Wirselkraut, my Dell Inspiron 6400).
*** CLISP 2.44
<example>
OBJCL[3]> (time (collect-classes))
Real time: 14.182854 sec.
Run time: 14.180886 sec.
Space: 189717640 Bytes
GC: 123, GC time: 2.412143 sec.
0
</example>
*** Allegro CL 8.1
<example>
OBJCL(3): (time (collect-classes))
; cpu time (non-gc) 2,660 msec user, 10 msec system
; cpu time (gc) 620 msec user, 0 msec system
; cpu time (total) 3,280 msec user, 10 msec system
; real time 3,291 msec
; space allocation:
; 1,816,989 cons cells, 44,365,376 other bytes, 72,082 static bytes
0
</example>
Note that Allegro CL defers class finalisation until the first instance
of a class is created. Then again, half the classes created are
metaclasses which are immediately instantiated, so the speed is
impressive, either way.
*** SBCL 1.0.14.debian
<example>
\* (time (collect-classes))
STYLE-WARNING:
slot names with the same SYMBOL-NAME but different SYMBOL-PACKAGE (possible
package problem) for class
#<OBJECTIVE-C-META-CLASS NS:++NS-OBJECT {B75E4440}>:
(SB-PCL::NAME NS:NAME)
STYLE-WARNING:
slot names with the same SYMBOL-NAME but different SYMBOL-PACKAGE (possible
package problem) for class #<NS:++NS-OBJECT NS:+NS-OBJECT {B75E4440}>:
(SB-PCL::NAME NS:NAME)
STYLE-WARNING:
slot names with the same SYMBOL-NAME but different SYMBOL-PACKAGE (possible
package problem) for class
#<NS:+NS-OBJECT NS:+GSXMLP-LIST-PARSER {B75ED3E0}>:
(SB-PCL::NAME NS:NAME)
</example>
That's how it starts off. The **style-warnings** go on and on. At first,
they rush by fast, but each new class seems to take more time than the
previous one to create. I'm not going to wait for it to complete in
order to tell you the **time** stats because it could well take decades...
*** CMUCL CVS 19d 19d-release (19D)
<example>
\* (time (collect-classes))
; Compiling LAMBDA NIL:
; Compiling Top-Level Form:
Type-error in KERNEL::INVALID-ARRAY-INDEX-ERROR-HANDLER:
4 is not of type (INTEGER 0 (0))
[Condition of type TYPE-ERROR]
Restarts:
0: [ABORT ] Return to Top-Level.
1: [DESTROY] Destroy the process
Debug (type H for help)
(MAPHASH #<Closure Over Function "DEFUN MAKE-ACCESSOR-TABLE" {5974DC09}>
#<HASH-TABLE :TEST EQ :WEAK-P NIL :COUNT 65 {2824FEBD}>)
Source:
; File: target:code/hash-new.lisp
(AREF KV-VECTOR (* 2 I))
0]
</example>
I don't have the slightest idea what that could mean. A bug in CMUCL's
hash table code?
*** <verbatim>LispWorks</verbatim> Personal 5.0.1
Not timed yet. Will do that someday.
*** So, What Is To Be Done?
I'll have to ask some CMUCL and SBCL gurus what's going on in their
respective variations of PCL.
* 2008-02-03, 10:45:58 CET
** To be compatible or not to be compatible?
I wonder whether being API-compatible with Clozure CL's Objective-C
bridge would be a good or bad thing. On the one hand, compatibility
means application portabilitiy, which is nice. On the other hand, using
the same package names and reader macros as Clozure CL's bridge makes it
hard to have both loaded at the same time.
Then again, if someone wants to compare Objective-CL with Clozure CL's
bridge side-by-side, it's their responsibility to rename packages as
needed. Reader macros aren't essential for using Objective-CL, so they
may be left disabled in such a case, anyway. I'm going to try hard to
use Objective-CL-specific package names within my own code
(i.e. **objective-c-classes** rather than **NS**), so renaming packages won't
break things.
I'll be opting for direct API compatibility for now. It seems to be the
right choice.
* 2008-01-29, 21:34:16 CET
In the Objective-C 2.0 runtime, the functions
=class_add{Method,Protocol,Ivar}=, =class_copyMethodList=,
={class,protocol}_copyProtocolList=, =protocol_copyMethodDescriptionList=,
and =class_copyPropertyList= are probably our friends.
=class_copyMethodList= may be used to together with
=method_setImplementation= for good effect.
But... What about the GNU runtime? Is it okay to inspect a Class'
=methods= member (see =objc.h= and =objc-api.h=) and change the =IMPs= that
the individual members point to? Is it possible to add new methods at
runtime by using =class_add_method_list= and thus
=ObjcUtilities_register_method_list=? Is the behaviour of these two
functions specified if a method list for a given class has already been
registered in the past? If so, do they replace the original list or
amend it?
Changing ivars after class creation seems generally impossible. This
applies to all the supported runtimes, and I think I can actually see
why. I don't think it's a serious problem, but I do consider it
regrettable.
* 2008-01-28, 20:45:35 CET
I've added the following files as a first step to support class
definition.
For the GNU runtime (from JIGS, the Java Interface to GNUstep):
- =JIGS/ObjcRuntimeUtilities.c=
- =JIGS/ObjcRuntimeUtilities.h=
- =JIGS/ObjcRuntimeUtilities2.m=
For the NeXT runtime (from <verbatim>PyObjC</verbatim>):
- =PyObjC/pyobjc-compat.h=
- =PyObjC/objc-runtime-compat.h=
- =PyObjC/objc-runtime-compat.m=
Both the JIGS and <verbatim>PyObjC</verbatim> codebases are impressively
modular. Those guys know what they're doing.
* 2008-01-27, 12:55:06 CET
Objective-CL now includes its own version of libffi, imported from an
older version of <verbatim>PyObjC</verbatim>. (The current version only
supports x86 and PowerPC.) Interestingly, according to the Web, Mac OS
X 10.5 includes its own version of libffi. This is really good news!
We can finally rest assured that Objective-CL does not break on Mac OS X
because of libffi bitrot anytime soon. In order to take advantage of
this new state of the world, the build system has been changed so as to
only compile our own version of libffi if we can't find any on the
system.
The downside? We now depend on autoconf. I don't consider this a
problem, though.
* 2007-10-10, 12:02:38 CEST
I've cleaned the Objective-C code up by making the NeXT and GNU
runtime-specific code converge a bit. This also makes **find-selector**
return **nil** for unknown selectors on the NeXT runtime, so compile-time
warnings about unknown methods are possible there now. The latter
relies on =sel_isMapped=, whose semantics are not entirely clear to me.
On the one hand, Apple's reference manual states: *“You can use this
function to determine whether a given address is a valid selector,”*
which I interpret as meaning that it takes a selector pointer as an
argument, not a string. On the other hand, in the preceding section,
the same document states: *“You can still use the sel_isMapped function
to determine whether a method name is mapped to a selector.”*
So if I have two strings that aren't the same under **pointer-eq**, but that
both name the same valid selector that is registered with the runtime,
like ="self"=, say, does =sel_isMapped= work reliably in this case? I'm not
sure.
On another note, I wonder what the difference between
=sel_get_uid/sel_getUid= and =sel_register_name/sel_registerName= might be.
They seem to do the same thing.
Maybe this whole =#ifdef= mess isn't even strictly necessary, anyway. I
could just copy =objc-gnu2next.h= from the GNUstep project (LGPLv3, so the
licensing is fine).
http://svn.gna.org/svn/gnustep/libs/base/trunk/Headers/Additions/GNUstepBase/objc-gnu2next.h
* 2007-10-04, 17:27:02 CEST
** `char' Does Actually Indicate a Char, Sometimes
The latest changes made the test cases fail on GNUstep/x86, which either
means that the <verbatim>PyObjC</verbatim> code is wrong, or the GNU
runtime has very weird calling conventions that use =ints= as wrappers for
=chars= or something. Anyway, I have reverted the changes for GNUstep and
left them in place for Mac OS X (but note that I left the
<verbatim>PyObjC</verbatim> code as it is, which means that libffi is
still directed to treats chars as ints). As a result, both NeXT/PowerPC
and GNUstep/x86 work for now, but I'm uncertain about the status of
other architectures as well as calling methods with chars and shorts as
arguments, which I've got no test cases for. I'm not confident that
either GNUstep/PowerPC/SPARC/whatever or NeXT/x86 work the way my code
expects them to.
* 2007-10-04, 16:52:32 CEST
** `char' Does Not Indicate a Char, Continued
There's a good chance that I've figured out what to do about the
=char/int= mess. As it turns out, it isn't even limited to =chars=, as
=shorts= are affected, too. According to the code I took from
<verbatim>PyObjC</verbatim>, specifically the typespec conversion
functions in =libffi_support.m=, both GNUstep and NeXT/PowerPC treat =chars=
and =shorts= as =ints=. The only platform that isn't brain-damaged in this
way seems to be NeXT/x86. Or maybe it's even more brain-damaged, as it
treats =shorts= and =chars= normally when they are used as arguments, but as
=ints= when they're used as return values! At least GNUstep and
NeXT/PowerPC are brain-damaged in a *consistent* manner.
I figure the reason I never saw this problem in GNUstep is probably
endianness. The little-endian x86 lets you treat pointers to =ints= as
pointers to =chars= without breaking anything, but that doesn't work in
big-endian machines.
* 2007-10-04, 13:02:31 CEST
** `char' Does Not Indicate a Char
In principle, the typespec "c" is supposed indicate a =char=. Now look at
the following SLIME session transcript (SBCL/PowerPC on Mac OS X):
<example>
OBJECTIVE-CL> (defparameter *tmp*
(invoke (find-objc-class 'ns-string)
:string-with-u-t-f-8-string "Mulk."))
*TMP*
OBJECTIVE-CL> (defparameter *tmp2*
(invoke (find-objc-class 'ns-string)
:string-with-u-t-f-8-string "Mulk."))
*TMP2*
OBJECTIVE-CL> (second ;return type specifier
(multiple-value-list
(retrieve-method-signature-info (find-objc-class 'ns-string)
(selector :is-equal))))
"c"
OBJECTIVE-CL> (invoke *tmp* :is-equal *tmp2*)
0
OBJECTIVE-CL> (primitive-invoke *tmp* :is-equal :char *tmp2*)
0
OBJECTIVE-CL> (primitive-invoke *tmp* :is-equal :int *tmp2*)
1
OBJECTIVE-CL> (primitive-invoke *tmp* :is-equal :long *tmp2*)
1
OBJECTIVE-CL> (primitive-invoke *tmp* :is-equal :long-long *tmp2*)
4294967296
</example>
Now, I see why the last value is bogus (I'd be surprised if it weren't,
actually), but why the heck is the correct value (1, because, you see,
the strings *are* equal and **+yes+** is 1 on my machine) returned only for
the wrong return type? The return type is specified as ="c"=, but it's
actually an =int=! What's going on here? And rather more importantly:
What can I do about this? I don't feel exactly comfortable about
cheating and treating ="c"= as specifying an =int= on all systems based on
the NeXT runtime without having any indication about what else there is
in the NeXT runtime that has to be special-cased. I haven't seen this
weird behaviour documented anywhere. Even this specific case is
non-trivial, for I don't know whether this applies to all =chars=, or only
to =chars= that are booleans, or only to =chars= that are returned, or even
only to =chars= that are returned *and* are actually booleans.
* 2007-09-26, 00:13:11 CEST
** Licensing
Licensing is another open question. For the moment, I'm releasing this
project under the terms of the GPLv3. This seems like a reasonable
choice, because it gives me the option of giving people more permissions
later by applying the LGPLv3 to my code. I must be aware that only I am
allowed to do this, though, and even then only if all contributors agree
(if someone actually makes a contribution, that is). I may want to
require all contributors to dual-license their contributions, or maybe
to make them available under the terms of the LGPLv3 in the first place
(though the latter would make marking them difficult).
* 2007-09-25, 20:59:40 CEST
** Value Conversion Madness
Open question: Should =NSArray= instances be converted to lists or arrays
automatically? If so, we ought to make functions like **objc-class-of**
behave in a reasonable way for those kinds of objects, i.e. return
=NSArray= or =NSMutableArray= (whatever it is that **invoke** makes out of them
when converting them into Objective-C instances again).
Note that we *must not* convert =NSMutableArray= instances or any other
mutable objects in this way! Note also that our decision *must* be based
on the dynamic type of the object, not the static one, because a method
whose return type is =NSArray= may as well return an =NSMutableArray= that
we've fed it sometime earlier. This is okay for immutable objects, but
mutable objects are bound to cause trouble when such a thing happens.
Related types of objects are strings (=NSString=), hash tables
(=NSDictionary=), and numbers (=NSNumber=).
Note that such behaviour would make it impossible to fully identify CLOS
classes with Objective-C classes, as arrays would have no Objective-C
class to belong to. Then again, why would you want to distinguish
Objective-C arrays from Lisp arrays in your Lisp code, anyway? Real
integration means not having to worry about such things.
On the other hand, conversion of large =NSArrays= may be prohibitively
expensive, so a switch is needed, either way. The real question is what
the default behaviour should look like.
There's an alternative to consider, too. For =NSArrays=, there is
Christophe Rhodes' user-extensible sequence proposal, but even without
support for that, we can provide a *conduit* (a package) that looks like
the **common-lisp** package, but overloads all sequence and hash-table
functions. Overloading all sequence functions might be a lot of work,
though.
* 2007-09-23, 17:09:07 CEST
** Improved Memory Management for the Masses
Up until now, the second-generation method invocation procedures
(**low-level-invoke** and **primitive-invoke**) simply called **make-instance** for
every object received from Objective-C, which meant that although a
lookup in the caching hash tables was done, method dispatch for
**make-instance** was needed. Therefore, everything just worked, but did so
slowly.
I realised yesterday, after having profiled the code and detected that
**make-instance** method dispatch was the speed bottleneck of **invoke** calls
now, that overriding **make-instance** wasn't really necessary for memory
management, as we could put instances into the hash tables and register
finalisers for them just after they were fully created.
So that's what I made the program do. One of the results is much
shorter and clearer code, but the more interesting one is a speed
improvement of around the factor 3, making 100'000 calls to
=NSMethodSignature#getArgumentTypeAtIndex:=, which previously called
**make-instance** for each returned value, take around 10s on my machine.
With the CFFI speed hack enabled, caching **cffi::parse-type** results, this
figure even goes down to around 2s (that's 50'000 method calls per
second).
I think that's pretty cool. I'm quite satisfied with method invocation
performance now. Compared to C, We're still off by a factor of 22 or so
(0.9s for 1'000'000 method calls). Most of the time is spent on memory
allocation for argument passing and typespec strings. By introducing a
global pool of preallocated memory spaces for these purposes (one
argument space per thread and maybe a bunch of string buffers, with a
fallback mechanism for method calls that take too much space), we might
be able to cut the run time by another factor of 5. After that, we
can't optimise the Lisp code any further, because most of the rest of
the time is spent within the Objective-C function
objcl_invoke_with_types (or maybe in calling it via CFFI, which would be
even worse, optimisationwise).
It's probably best not to spend too much time pondering this, though,
because without the CFFI speed hack, the improvement would probably not
be noticeable, anyway (**cffi::parse-type** is most often called by
**cffi:mem-ref** and **cffi:mem-aref**, not by the allocation routines).
** Milestones Lying Ahead
There are three things left to do that are showstoppers against actually
using Objective-CL productively. One is support for structs. This one
is actually quite a bit harder than it looks, because we don't
necessarily know the structure of foreign objects. Objective-C tells us
about the structure (though not the member naming!) of structures as
well as pointers to structures that are returned by methods, but any
more indirection (that is, pointers to pointers to structs or something
even hairier) makes the Objective-C runtime conceal the internals of the
structs pointed to. This is probably not a problem in practise, though,
as pointers to pointers to structs will usually mean a pointer that the
user may alter in order to point to other structs, not that the user
will access the structs that are pointed to. In fact, it will probably
be best to just pass pointers on to the user.
The second thing left to do is support for defining Objective-C classes.
I think this is going to be hard. I've not looked at the problem in
detail yet, but it looks like creating methods and classes, and
registering methods and classes are all different actions that are all
handled differently depending on the runtime. In the case of GNUstep, I
don't even know how to register new selectors yet.
Third, varargs. These are easy to implement, but I'm not sure how they
should look like in the case of **invoke**. Maybe a special keyword
indicator like =:*= would work for indicating the end of the method name,
but I think that could be a bit ugly.
We shall see.
** OpenMCL and Objective-CL Compared
On another note, I briefly checked out OpenMCL's support for Objective-C
by randomly typing a bunch of method invocations into the listener and
calling **apropos** a lot. Here's what stuck:
1. You have to explicitely create selectors by using **@selector**. Why is
that? What's wrong with symbols and strings?
2. Strings designate only =NSString= objects, not C strings. Why?
3. The bridge is just as fast as I expect using libffi from C to be on
that machine, that is, more than 20 times as fast as Objective-CL
with the speed hack enabled (250'000 method calls per second; my
Inspiron is faster, so don't compare this value to the ones above).
4. There's no **find-objc-class**, but **find-class** works. Objective-C
classes seem to be normal CLOS classes whose names are found in the
NS package.
All in all, what struck me the most was the fact that the OpenMCL
Objective-C bridge does not seem to make use of the concept of
designators as much as Objective-CL does. You have to define C strings
and selectors explicitely, which I consider a minor annoyance. It's
faster, though. Then again, considering that it's integrated into the
compiler, I was bit disappointed by the speed, because I figured that a
native-code compiler could do better than libffi (which is still a lot
slower than directly calling stuff from Objective-C).
** By The Way
Gorm rules. We need to make Objective-CL fully Gorm-compatible.
----
*Matthias Benkard, <lisp>(format-time-string "%Y-%m-%d, %k:%M %Z")</lisp>*
http://matthias.benkard.de/
; Local Variables:
; mode: muse
; End:
|