• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

aozhimin/iOS-Monitor-Platform: iOS 性能监控 SDK —— Wedjat(华狄特)开发过程的 ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

aozhimin/iOS-Monitor-Platform

开源软件地址:

https://github.com/aozhimin/iOS-Monitor-Platform

开源编程语言:


开源软件介绍:

Monitor

为了让这篇文章能够在公众号发表,所以将文章拆解成上下两篇:基础性能篇和网络篇

目录

为什么写这篇文章?

随着移动互联网向纵深发展,用户变得越来越关心应用的体验,开发者必须关注应用性能所带来的用户流失问题。据统计,有十种应用性能问题危害最大,分别为:连接超时、闪退、卡顿、崩溃、黑白屏、网络劫持、交互性能差、CPU 使用率问题、内存泄露、不良接口。开发者难以兼顾所有的性能问题,而在传统的开发流程中,我们解决性能问题的方式通常是在得到线上用户的反馈后,再由开发人员去分析引发问题的根源;显然,凭借用户的反馈来得知应用的性能问题这种方式很原始,也很不高效,它使得开发团队在应对应用性能问题上很被动;所以寻找一种更专业和高效的手段来保障应用的性能就变得势在必行。性能监控 SDK 的定位就是帮助开发团队快速精确地定位性能问题,进而推动应用的性能和用户体验的提升。

这篇文章是我在开发 iOS 性能监控平台 SDK 过程前期的调研和沉淀。主要会探讨在 iOS 平台下如何采集性能指标,如 CPU 占用率、内存使用情况、FPS、冷启动、热启动时间,网络,耗电量等,剖析每一项性能指标的具体实现方式,SDK 的实现会有一定的技术难度,这也是我为什么写这篇文章的原因,我希望能够将开发过程中的一些心得和体会记录下来,同时后续我会将实现 SDK 的详细细节开源出来,希望能对读者有所帮助。

项目名称的来源

我们团队将这个项目命名为 Wedjat(华狄特),取自古埃及神话中鹰头神荷鲁斯的眼睛,荷鲁斯是古埃及神话中法老的守护神,他通常被描绘成“隼头人身”的形象,最常见的代表符号是一只眼睛,该眼也被称之为“荷鲁斯之眼”,象征着“正义之眼”,严厉、公正、铁面无私,一切公开或私人的行为,都逃不过他的法眼。他不但是光明和天堂的象征,最早还是一位生育万物的大神,每天在尼罗河上巡视他的子民。Wedjat 的寓意恰好与我们性能监控 SDK 的愿景相契合。

荷鲁斯之眼又称真知之眼、埃及乌加眼,是一个自古埃及时代便流传至今的符号,也是古埃及文化中最令外人印象深刻的符号之一。荷鲁斯之眼顾名思义,它是鹰头神荷鲁斯的眼睛。荷鲁斯的右眼象征完整无缺的太阳,依据传说,因荷鲁斯战胜赛特,右眼有着远离痛苦,战胜邪恶的力量,荷鲁斯的左眼象征有缺损的月亮,依据传说,荷鲁斯后来将左眼献给欧西里斯,因而左眼亦有分辨善恶、捍卫健康与幸福的作用,亦使古埃及人也相信荷鲁斯的左眼具有复活死者的力量。

CPU

A CPU chip is designed for portable computers, it is typically housed in a smaller chip package, but more importantly, in order to run cooler, it uses lower voltages than its desktop counterpart and has more "sleep mode" capability. A mobile processor can be throttled down to different power levels or sections of the chip can be turned off entirely when not in use. Further, the clock frequency may be stepped down under low processor loads. This stepping down conserves power and prolongs battery life.

CPU 是移动设备最重要的计算资源,设计糟糕的应用可能会造成 CPU 持续以高负载运行,一方面会导致用户使用过程遭遇卡顿;另一方面也会导致手机发热发烫,电量被快速消耗完,严重影响用户体验。

APP 的 CPU 占用率

如果想避免出现上述情况,可以通过监控应用的 CPU 占用率,那么在 iOS 中如何实现 CPU 占用率的监控呢?事实上,学习过操作系统课程的读者都了解线程是调度和分配的基本单位,而应用作为进程运行时,包含了多个不同的线程,显然如果我们能获取应用的所有线程占用 CPU 的情况,也就能知道应用的 CPU 占用率。

iOS 是基于 Apple Darwin 内核,由 kernel、XNU 和 Runtime 组成,而 XNU 是 Darwin 的内核,它是“X is not UNIX”的缩写,是一个混合内核,由 Mach 微内核和 BSD 组成。Mach 内核是轻量级的平台,只能完成操作系统最基本的职责,比如:进程和线程、虚拟内存管理、任务调度、进程通信和消息传递机制。其他的工作,例如文件操作和设备访问,都由 BSD 层实现。

上图是权威著作《OS X Internal: A System Approach》给出的 Mac OS X 中进程子系统组成的概念图,与 Mac OS X 类似,iOS 的线程技术也是基于 Mach 线程技术实现的,在 Mach 层中 thread_basic_info 结构体提供了线程的基本信息。

struct thread_basic_info {
        time_value_t    user_time;      /* user run time */
        time_value_t    system_time;    /* system run time */
        integer_t       cpu_usage;      /* scaled cpu usage percentage */
        policy_t        policy;         /* scheduling policy in effect */
        integer_t       run_state;      /* run state (see below) */
        integer_t       flags;          /* various flags (see below) */
        integer_t       suspend_count;  /* suspend count for thread */
        integer_t       sleep_time;     /* number of seconds that thread
                                           has been sleeping */
};

任务(task)是一种容器(container)对象,虚拟内存空间和其他资源都是通过这个容器对象管理的,这些资源包括设备和其他句柄。严格地说,Mach 的任务并不是其他操作系统中所谓的进程,因为 Mach 作为一个微内核的操作系统,并没有提供“进程”的逻辑,而只是提供了最基本的实现。不过在 BSD 的模型中,这两个概念有1:1的简单映射,每一个 BSD 进程(也就是 OS X 进程)都在底层关联了一个 Mach 任务对象。

上面引用的是《OS X and iOS Kernel Programming》对 Mach task 的描述,Mach task 可以看作一个机器无关的 thread 执行环境的抽象 一个 task 包含它的线程列表。内核提供了 task_threads API 调用获取指定 task 的线程列表,然后可以通过 thread_info API 调用来查询指定线程的信息,thread_info API 在 thread_act.h 中定义。

kern_return_t task_threads
(
	task_t target_task,
	thread_act_array_t *act_list,
	mach_msg_type_number_t *act_listCnt
);

task_threadstarget_task 任务中的所有线程保存在 act_list 数组中,数组中包含 act_listCnt 个条目。

kern_return_t thread_info
(
	thread_act_t target_act,
	thread_flavor_t flavor,
	thread_info_t thread_info_out,
	mach_msg_type_number_t *thread_info_outCnt
);

thread_info 查询 flavor 指定的 thread 信息,将信息返回到长度为 thread_info_outCnt 字节的 thread_info_out 缓存区中,

有了上面的铺垫后,得到获取当前应用的 CPU 占用率的实现如下:

#import <mach/mach.h>
#import <assert.h>

+ (CGFloat)appCpuUsage {
    kern_return_t kr;
    task_info_data_t tinfo;
    mach_msg_type_number_t task_info_count;
    
    task_info_count = TASK_INFO_MAX;
    kr = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;
    
    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;
    
    thread_basic_info_t basic_info_th;
    
    // get threads in the task
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    long total_time     = 0;
    long total_userTime = 0;
    CGFloat total_cpu   = 0;
    int j;
    
    // for each thread
    for (j = 0; j < (int)thread_count; j++) {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                         (thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;
        
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            total_time     = total_time + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
            total_userTime = total_userTime + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
            total_cpu      = total_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * kMaxPercent;
        }
    }
    
    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);
    
    return total_cpu;
}

在调用 task_threads API 时,target_task 参数传入的是 mach_task_self(),表示获取当前的 Mach task。而在调用 thread_info API 时,flavor 参数传的是 THREAD_BASIC_INFO ,使用这个类型会返回线程的基本信息,定义在 thread_basic_info_t 结构体,包含了用户和系统的运行时间,运行状态和调度优先级。

注意方法最后要调用 vm_deallocate,防止出现内存泄漏。据测试,该方法采集的 CPU 数据和腾讯的 GTInstruments 数据接近。

由于监控 CPU 的线程也会占用 CPU 资源,所以为了让结果更客观,可以考虑在计算的时候将监控线程排除。

下面是 GT 中获得 App 的 CPU 占用率的方法

- (float)getCpuUsage
{
    kern_return_t           kr;
    thread_array_t          thread_list;
    mach_msg_type_number_t  thread_count;
    thread_info_data_t      thinfo;
    mach_msg_type_number_t  thread_info_count;
    thread_basic_info_t     basic_info_th;
    
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    cpu_usage = 0;
    
    for (int i = 0; i < thread_count; i++)
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[i], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;

        if (!(basic_info_th->flags & TH_FLAGS_IDLE))
        {
            cpu_usage += basic_info_th->cpu_usage;
        }
    }
    
    cpu_usage = cpu_usage / (float)TH_USAGE_SCALE * 100.0;
    
    vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    
    return cpu_usage;
}

总的 CPU 占用率

而获取整个设备的 CPU 占用率如下:

static NSUInteger const kMaxPercent = 100;

+ (CGFloat)cpuUsage {
    CGFloat cpuUsage = 0;
    processor_info_array_t _cpuInfo, _prevCPUInfo = nil;
    mach_msg_type_number_t _numCPUInfo, _numPrevCPUInfo = 0;
    unsigned _numCPUs;
    NSLock *_cpuUsageLock;
    
    int _mib[2U] = {CTL_HW, HW_NCPU};
    size_t _sizeOfNumCPUs = sizeof(_numCPUs);
    int _status = sysctl(_mib, 2U, &_numCPUs, &_sizeOfNumCPUs, NULL, 0U);
    if (_status)
        _numCPUs = 1;
    
    _cpuUsageLock = [[NSLock alloc] init];
    
    natural_t _numCPUsU = 0U;
    kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &_numCPUsU, &_cpuInfo, &_numCPUInfo);
    if (err == KERN_SUCCESS) {
        [_cpuUsageLock lock];
        
        for (unsigned i = 0U; i < _numCPUs; ++i) {
            CGFloat _inUse, _total = 0;
            if (_prevCPUInfo) {
                _inUse = (
                          (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER]   - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER])
                          + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM])
                          + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE]   - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE])
                          );
                _total = _inUse + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE]);
            } else {
                _inUse = _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER] + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM] + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE];
                _total = _inUse + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE];
            }
            
            if (_total != 0) {
                cpuUsage += _inUse / _total;
            }
        }
        
        [_cpuUsageLock unlock];
        if (_prevCPUInfo) {
            size_t prevCpuInfoSize = sizeof(integer_t) * _numPrevCPUInfo;
            vm_deallocate(mach_task_self(), (vm_address_t)_prevCPUInfo, prevCpuInfoSize);
        }
        return cpuUsage * kMaxPercent ;
    } else {
        return -1;
    }
}

上述方法大致思路是先计算出每个 CPU 核心的占用率,然后将所有 CPU 核心的占用率相加得到设备总的 CPU 占用率,这主要参考 top 命令,它在计算多核 CPU 的占用率时,是把每个核的 CPU 占用率求和。

网上有很多文章都是通过上述方式去获取设备的 CPU 占用率,包括 YYCategoriesUIDeviceYYAdd category 也是采用这种方式,但是其实计算出来的 CPU 占用率会维持一个值基本没有改变,要归功于 ySssssssss 发现这个细节。上面这段代码其实存在问题,代码中的 _prevCPUInfo_numPrevCPUInfo 等使用的是局部变量,这会造成对 _prevCPUInfo 非空的判断总是为假,最终计算 _cpuInfo_prevCPUInfo 差值的那段代码根本不会执行。可以通过将这几个变量改为成员变量,或者使用静态变量。成员变量的写法如下:

@implementation WDTDevice {
    processor_info_array_t _cpuInfo, _prevCPUInfo;
    mach_msg_type_number_t _numCPUInfo, _numPrevCPUInfo;
    NSLock *_cpuUsageLock;
}

- (CGFloat)cpuUsage {
    CGFloat cpuUsage = 0;
    unsigned _numCPUs;
    
    int _mib[2U] = {CTL_HW, HW_NCPU};
    size_t _sizeOfNumCPUs = sizeof(_numCPUs);
    int _status = sysctl(_mib, 2U, &_numCPUs, &_sizeOfNumCPUs, NULL, 0U);
    if (_status)
        _numCPUs = 1;
    
    _cpuUsageLock = [[NSLock alloc] init];
    
    natural_t _numCPUsU = 0U;
    kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &_numCPUsU, &_cpuInfo, &_numCPUInfo);
    if (err == KERN_SUCCESS) {
        [_cpuUsageLock lock];
        
        for (unsigned i = 0U; i < _numCPUs; ++i) {
            CGFloat _inUse, _total = 0;
            if (_prevCPUInfo) {
                _inUse = (
                          (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER]   - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER])
                          + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM])
                          + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE]   - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE])
                          );
                _total = _inUse + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE]);
            } else {
                _inUse = _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER] + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM] + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE];
                _total = _inUse + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE];
            }
            
            if (_total != 0) {
                cpuUsage += _inUse / _total;
            }
        }
        
        [_cpuUsageLock unlock];
        if (_prevCPUInfo) {
            size_t prevCpuInfoSize = sizeof(integer_t) * _numPrevCPUInfo;
            vm_deallocate(mach_task_self(), (vm_address_t)_prevCPUInfo, prevCpuInfoSize);
        }
        
        _prevCPUInfo = _cpuInfo;
        _numPrevCPUInfo = _numCPUInfo;
        
        _cpuInfo = NULL;
        _numCPUInfo = 0U;

        return cpuUsage * kMaxPercent ;
    } else {
        return -1;
    }
}

改为这种写法之后发现结果几乎都在 100% 以上,所以这种写法依然存在问题。

于是寻找到另外一种 host_statistics 函数拿到 host_cpu_load_info 的值,这个结构体的成员变量 cpu_ticks 包含了 CPU 运行的时钟脉冲的数量,cpu_ticks 是一个数组,里面分别包含了 CPU_STATE_USER, CPU_STATE_SYSTEM, CPU_STATE_IDLECPU_STATE_NICE 模式下的时钟脉冲。

+ (CGFloat)cpuUsage {
    kern_return_t kr;
    mach_msg_type_number_t count;
    static host_cpu_load_info_data_t previous_info = {0, 0, 0, 0};
    host_cpu_load_info_data_t info;
    
    count = HOST_CPU_LOAD_INFO_COUNT;
    
    kr = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&info, &count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    natural_t user   = info.cpu_ticks[CPU_STATE_USER] - previous_info.cpu_ticks[CPU_STATE_USER];
    natural_t nice   = info.cpu_ticks[CPU_STATE_NICE] - previous_info.cpu_ticks[CPU_STATE_NICE];
    natural_t system = info.cpu_ticks[CPU_STATE_SYSTEM] - previous_info.cpu_ticks[CPU_STATE_SYSTEM];
    natural_t idle   = info.cpu_ticks[CPU_STATE_IDLE] - previous_info.cpu_ticks[CPU_STATE_IDLE];
    natural_t total  = user + nice + system + idle;
    previous_info    = info;
    
    return (user + nice + system) * 100.0 / total;
}

上面代码通过计算 infoprevious_info 的差值,分别得到在这几个模式下的 cpu_ticks,除 idle 以外都属于 CPU 被占用的情况,最后就能求出 CPU 的占用率。

经测试发现这种计算总的 CPU 占用率的方式与 iOS 系统的 top 命令的值吻合(测试环境:iPhone 5s 的越狱机器),并且 App Store 中的几个性能工具的应用都是采用这种方式去计算设备的 CPU 占用率的,比如简易系统状态Battery Memory System Status Monitor 这两款应用。

CPU 核数

+ (NSUInteger)cpuNumber {
    return [NSProcessInfo processInfo].activeProcessorCount;
}

CPU 频率

CPU 频率,就是 CPU 的时钟频率, 是 CPU 运算时的工作的频率(1秒内发生的同步脉冲数)的简称。单位是 Hz,它决定移动设备的运行速度。

在 iOS 中与 CPU 频率相关的性能指标有三个:CPU 频率,CPU 最大频率 和 CPU 最小频率。

下面代码给出了获取 CPU 频率的实现,笔者通过反编译发现手淘,腾讯视频等应用也是通过这种方式获取 CPU 频率,反编译的截图如下。

上面反编译代码的实现效果和下面这段代码基本一致。

+ (NSUInteger)getSysInfo:(uint)typeSpecifier {
    size_t size = sizeof(int);
    int results;
    int mib[2] = {CTL_HW, typeSpecifier};
    sysctl(mib, 2, &results, &size, NULL, 0);
    return (NSUInteger)results;
}

+ (NSUInteger)getCpuFrequency {
    return [self getSysInfo:HW_CPU_FREQ];
}

反编译代码中的 [self getSysInfo:0Xf] 的参数 0Xf 就是 HW_CPU_FREQHW_CPU_FREQ 的宏定义的就是 15.

但是在真机测试会发现上述方式并不能正确获取到设备的 CPU 频率,如果你在网上搜索会发现有很多代码都是使用这种方式,猜测应该是早期版本还是能够获取到的,只不过出于安全性的考虑,主频这个内核变量也被禁止访问了。手淘等应用中代码估计应该是遗留代码。 既然上述方式已经被 Apple 堵死了,我们还有其他的方法可以获取到 CPU 主频吗?当然,其实我们还是可以通过一些变通的方式获取到的,主要有以下两种方式。 第一种方式是比较容易实现,我们通过硬编码的方式,建立一张机型和 CPU 主频的映射表,然后根据机型找到对应的 CPU 主频即可。

static const NSUInteger CPUFrequencyTable[] = {
    [iPhone_1G]         = 412,
    [iPhone_3G]         = 620,
    [iPhone_3GS]        = 600,
    [iPhone_4]          = 800,
    [iPhone_4_Verizon]  = 800,
    [iPhone_4S]         = 800,
    [iPhone_5_GSM]      = 1300,
    [iPhone_5_CDMA]     = 1300,
    [iPhone_5C]         = 1000,
    [iPhone_5S]         = 1300,
    [iPhone_6]          = 1400,
    [iPhone_6_Plus]     = 1400,
    [iPhone_6S]         = 1850,
    [iPhone_6S_Plus]    = 1850,
    [iPod_Touch_1G]     = 400,
    [iPod_Touch_2G]     = 533,
    [iPod_Touch_3G]     = 600,
    [iPod_Touch_4G]     = 800,
    [iPod_Touch_5]      = 1000,
    [iPad_1]            = 1000,
    [iPad_2_CDMA]       = 1000,
    [iPad_2_GSM]        = 1000,
    [iPad_2_WiFi]       = 1000,
    [iPad_3_WiFi]       = 1000,
    [iPad_3_GSM]        = 1000,
    [iPad_3_CDMA]       = 1000,
    [iPad_4_WiFi]       = 1400,
    [iPad_4_GSM]        = 1400,
    [iPad_4_CDMA]       = 1400,
    [iPad_Air]          = 1400,
    [iPad_Air_Cellular] = 1400,
    [iPad_Air_2]        = 1500,
    [iPad_Air_2_Cellular] = 1500,
    [iPad_Pro]          = 2260,
    [iPad_Mini_WiFi]    = 1000,
    [iPad_Mini_GSM]     = 1000,
    [iPad_Mini_CDMA]    = 1000,
    [iPad_Mini_2]       = 1300,
    [iPad_Mini_2_Cellular] = 1300,
    [iPad_Mini_3]       = 1300,
    [iPad_Mini_3_Cellular] = 1300,
    [iUnknown]          = 0
};

上面主频值的单位为 MHZ,SystemMonitor 就是使用这种方式。

第二种方式实现起来较上一种方式更为复杂,可以通过计算来得出 CPU 频率,具体的代码如下

extern int freqTest(int cycles);

static double GetCPUFrequency(void)
{
    volatile NSTimeInterval times[500];
    
    int sum = 0;
    
    for(int i = 0; i < 500; i++)
    {
        times[i] = [[NSProcessInfo processInfo] systemUptime];
        sum += freqTest(10000);
        times[i] = [[NSProcessInfo processInfo] systemUptime] - times[i];
    }
    
    NSTimeInterval time = times[0];
    for(int i = 1; i < 500; i++)
    {
        if(time > times[i])
            time = times[i];
    }
    
    double freq = 1300000.0 / time;
    return freq;
}

出于效率的考虑,代码中 freqTest 这个函数是用汇编写的,在工程加入一个文件 cpuFreq.s,后缀 s 代表这个文件是一个汇编文件,文件的代码如下:

.text
.align 4
.globl _freqTest    

_freqTest:

    push    {r4-r11, lr}

freqTest_LOOP:

    // loop 1
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 2
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 3
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 4
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 5
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 6
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 7
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 8
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 9
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    // loop 10
    add     r2, r2, r1
    add     r3, r3, r2
    add     r4, r4, r3
    add     r5, r5, r4
    add     r6, r6, r5
    add     r7, r7, r6
    add     r8, r8, r7
    add     r9, r9, r8
    add     r10, r10, r9
    add     r11, r11, r10
    add     r12, r12, r11
    add     r14, r14, r12
    add     r1, r1, r14

    subs    r0, r0, #1
    bne     freqTest_LOOP
    pop     {r4-r11, pc}

当然这个文件的汇编指令只支持 armv7 和 armv7s ,也就是 32 位 Arch,64 位汇编指令有机会再补上,如果你使用的是 64 位机器调试,记得将 build archive architecture only 设置为 NO 如下图,否则会编译不通过,

我用一台 iPhone 6 测试了这种方法获得 CPU 频率,结果为 1391614727.725209 HZ,大约也就是 1400 MHZ,和上面那张表中主频一致。

这种实现方式的代码实际是参考了 AppStore 上的一款应用 CPU Dasher,代码参考CPU-Dasher-for-iOS

要获取 CPU 最大频率 和 CPU 最小频率这两个性能指标也需要用到 sysctlsysctl 是用以查询内核状态的接口,具体实现如下

static inline Boolean WDTCanGetSysCtlBySpecifier(char* specifier, size_t *size) {
    if (!specifier || strlen(specifier) == 0 ||
        sysctlbyname(specifier, NULL, size, NULL, 0) == -1 || size == -1) {
        return false;
    }
    return
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap