在 iPhone 上的 objc2.0 中重新实现 objc_msgSend() 及其同级函数

发布于 2024-11-10 18:45:11 字数 1341 浏览 2 评论 0原文

我想做这样的事情:

#import <sys/time.h>

typedef long long int64;

int64 int64Micro(void)
{
    struct timeval timestruct;
    gettimeofday(&timestruct, NULL);
    return ((int64)timestruct.tv_sec)*((int64)1000000)+(int64)timestruct.tv_usec;
}

int64 lasttime = 0;

id objc_msgSend(id self, SEL op, ...)
{
    int64 starttime = int64Micro();
    va_list argptr;
    va_start(argptr, op);
    id retVal = 0;//objc_msgSendv(self, op, method_getSizeOfArguments(op), argptr);
    va_end(argptr);
    int64 cost = int64Micro()-starttime;
    if(cost > 1000)
        NSLog(@"%@() : %lld µs\n", NSStringFromSelector(op), cost);
    return retVal;
}

当它变得非常昂贵时,能够在代码的中心位置记录每个方法(1.000μs或1毫秒只是一个示例值,当然可以调整,但是不应该太小,否则日志记录将花费更多时间,因为对于大多数方法来说,方法的执行(被记录)。

由于在 iPhone 上动态库是不可能的,因此无法替换由预编译框架调用的 objc_msgSend 实现,而无需重新编译它们,但在我的代码中实际上调用的是我的实现,而不是 objc- 中的实现。运行时。

但这行

id retVal = 0;//objc_msgSendv(self, op, method_getSizeOfArguments(op), argptr)

被注释掉了,因为它应该在 objc 1.0 中工作,但在 objc 2.0 中 objc_msgSendv() 不再可用,与 method_getSizeOfArguments() 相同。

那么,有没有什么方法可以做到这一点,而不必重建 objc 运行时,也不必通过从源代码复制并粘贴数千个依赖于平台的汇编代码行来重新实现 objc_msgSend 及其兄弟的原始行为。 objc 运行时?

我已经考虑过 GCC 的 __builtin_apply() 函数来调用原始的 objc_msgSend(),但似乎没有办法知道传递给 objc_msgSend() 的变量参数的大小(以字节为单位)以进行特定的调用。

I want to do something like this:

#import <sys/time.h>

typedef long long int64;

int64 int64Micro(void)
{
    struct timeval timestruct;
    gettimeofday(×truct, NULL);
    return ((int64)timestruct.tv_sec)*((int64)1000000)+(int64)timestruct.tv_usec;
}

int64 lasttime = 0;

id objc_msgSend(id self, SEL op, ...)
{
    int64 starttime = int64Micro();
    va_list argptr;
    va_start(argptr, op);
    id retVal = 0;//objc_msgSendv(self, op, method_getSizeOfArguments(op), argptr);
    va_end(argptr);
    int64 cost = int64Micro()-starttime;
    if(cost > 1000)
        NSLog(@"%@() : %lld µs\n", NSStringFromSelector(op), cost);
    return retVal;
}

to be able to log for every method at a central place in code, when it is getting very expensive (the 1.000µs or 1 ms is just an example-value, can be adjusted of course, but should not be to small, as otherwise the logging will cost more time as the execution of the method, being logged, for most methods).

As on iPhone dynamic libs aren't possible, there is no way, to replace the objc_msgSend-implementation, which is called by precompiled frameworks, without recompiling them, but in my code actually my implementation is called instead of the one from the objc-runtime.

But this line

id retVal = 0;//objc_msgSendv(self, op, method_getSizeOfArguments(op), argptr)

is commented out, as it should work in objc 1.0, but in objc 2.0 objc_msgSendv() isn't available anymore, same as method_getSizeOfArguments().

So, is there any way to do this, without having to rebuild the objc runtime and without having to re-implement the original behavior of objc_msgSend and it's siblings by copy-and paste those thousands of platform dependent assembly code lines from the source of the objc runtime?

I already thought about GCC's __builtin_apply() function, to call the original objc_msgSend(), but it seems, there is no way to know the size in bytes of the variable parameters passed to objc_msgSend() for a certain call to it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

静谧幽蓝 2024-11-17 18:45:11

出于某些原因,用 objc_msgSend 来衡量性能并不是一个好主意。

  1. 实际上有许多 objc_msgSend 变体,具体取决于参数类型和返回类型。您必须重新实施所有这些。
  2. 您的应用程序的性能可能会大幅下降。当您尝试衡量性能时,这并不是最好的方法。
  3. 对于某些选择器,编译器实际上发出一个 objc_msgSend_fixup 函数指针,然后动态地优化为 vtable 查找。这完全避免了传统的 objc_msgSend。你也必须抓住所有这些。
  4. objc_msgSend 无法在 C 中实现,因为它实际上不是可变参数函数,并且没有可以用 C 表示的原型。从调用者的角度来看,objc_msgSend 是一个不透明蹦床;它将分派到方法 IMP,该方法本身预计会实现调用者所需的函数 ABI。

有很多更好的方法来衡量绩效。

如果 Instruments 没有显示慢速区域,则您可能只分析正在运行的线程而不是所有线程。使用时间分析器工具,单击轨道标题旁边的小“i”,然后选择“所有线程状态”。这将确保等待 I/O 所花费的时间计入您的测量中,默认情况下不计入该时间。

分析所有线程状态

如果由于某种原因仍然无法正常工作,而您只想记录所有选择器的硬列表运行时间,您可以使用 objc DTrace 提供程序添加 DTrace 探针来记录每个 Objective-C 方法的运行时间。它不会像 Instruments 那样记录 C 函数的统计信息,也不会分层组织事物,但如果 Instruments 没有为您做这件事,它会这样做。在仪器中,选择仪器 -> 跟踪符号。在出现的窗口中,输入以下内容:

-[* *]

即,跟踪在任何选择器名称 (*) 的任何类 (*) 上调用的所有实例方法 (-) )。然后记录一下,你就会得到每个方法所消耗时间的列表。此外,这不会击败内置 objc_msgSend 中的手动调整汇编器,并且受到 Apple 的完全支持。它也不仅限于您的代码;它还会分析 Apple 框架内的调用。

遗憾的是,iOS 设备当前不支持 DTrace 探针,因此您必须对模拟器中运行的应用程序使用 DTrace 探针。这显然并不理想。不过,如果您确实有一个方法作为热点,那么它很可能也会出现在模拟器中。

不过,时间概况工具更适合这项工作,而且确实有效。打开所有线程状态,您就会看到您的问题。

Swapping out objc_msgSend to measure performance like this is a bad idea for a few reasons.

  1. There are actually a number of objc_msgSend variants depending on the parameter types and return types. You'd have to re-implement all of those.
  2. The performance of your app is likely to degrade massively. When you're trying to measure performance, this isn't the best way to do it.
  3. For certain selectors, the compiler actually issues a objc_msgSend_fixup function pointer, which is then dynamically optimized into a vtable lookup. This avoids the traditional objc_msgSend entirely. You'd have to catch all of those too.
  4. objc_msgSend can not be implemented in C, as it is not actually variadic function and does not have a prototype that can be represented in C. From the caller's perspective, objc_msgSend is an opaque trampoline; it will dispatch to the method IMP, which is itself expected to implement the function ABI required by the caller.

There are much better ways to measure performance.

If Instruments isn't showing your slow areas, you may be profiling only running threads instead of all threads. Using the Time Profiler instrument, click the little 'i' next to the track header, and then select "All Thread States". This will make sure that time spent waiting on I/O is counted in your measurements, which by default isn't counted.

Profiling all thread states

If for some reason that still isn't working and you just want to record a hard list of all selector running times, you could add a DTrace probe using the objc DTrace provider to record the running time of every Objective-C method. This wouldn't record statistics for C functions like Instruments will, and it won't organize things hierarchically, but if Instruments isn't doing it for you, this will. In Instruments, choose Instrument -> Trace Symbol. In the window that appears, type the following:

-[* *]

That is, trace all instance methods (-) called on any class (*) of any selector name (*). Then record, and you'll get a list of the time consumed in every method. Further, this does not defeat the hand-tuned assembler in the built-in objc_msgSend, and is fully supported by Apple. It is also not limited to only your code; it will profile calls inside Apple's frameworks as well.

Unfortunately, DTrace probes are not currently supported on iOS devices, so you'd have to use the DTrace probe against an app running in the simulator. This is obviously not ideal. If you've really got a single method serving as a hot spot, though, it's likely that it will show up in the simulator as well.

Again, though, the Time Profile instrument is far better suited to the job, and it definitely works. Turn on All Thread States, and you'll see your problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文