NSLog效率低下的原因以及NSLog宏定义

2023-11-10

我是前言

打Log是我们debug时最简单朴素的方法, NSLog 对于objc开发就像 printf 对于c一样重要。但在使用 NSLog 打印大量Log,尤其是在游戏开发时(如每一帧都打印数据), NSLog 会明显的拖慢程序的运行速度(游戏帧速严重下滑)。本文探究了一下 NSLog 如此之慢的原因,并尝试使用lldb断点调试器替代NSLog进行debug log。

小测试

测试下分别使用 NSLog printf 打印10000次耗费的时间。 CFAbsoluteTimeGetCurrent() 函数可以打印出当前的时间戳,精度还是很高的,于是乎测试代码如下:
CFAbsoluteTime startNSLog = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < 10000; i++) {
    NSLog(@"%d", i);
}
CFAbsoluteTime endNSLog = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime startPrintf = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < 10000; i++) {
    printf("%d\n", i);
}
CFAbsoluteTime endPrintf = CFAbsoluteTimeGetCurrent();
NSLog(@"NSLog time: %lf, printf time: %lf", endNSLog - startNSLog, endPrintf - startPrintf);
这个时间和机器肯定有关系,只看它们的差别就好。为了全面性,尝试了三种平台:
NSLog time: 4.985445, printf time: 0.084193 // mac
NSLog time: 5.562460, printf time: 0.019408 // 模拟器
NSLog time: 10.471490, printf time: 0.090503 // 真机调试(iphone5)
可以发现,在 mac 上(模拟器其实也算是 mac 吧)速度差别达到了 60 倍左右,而真机调试甚至达到了离谱的 100 多倍。
探究原因
基本上这种事情一定可以在Apple文档中找到,看 NSLog 的文档,第一句话就说: Logs an error message to the Apple System Log facility. ,所以首先, NSLog 就不是设计作为普通的debug log的,而是error log;其次, NSLog 也并非是 printf 的简单封装,而是 Apple System Log (后面简称ASL)的封装。 

ASL

ASL是个啥?从 官方手册 上,或者从终端执行 man 3 asl 都可以看到说明: 
These routines provide an interface to the Apple System Log facility. They are intended to be a
replacement for the syslog(3) API, which will continue to be supported for backwards compatibility. 
大概就是个系统级别的log工具吧,syslog的替代版,提供了一系列强大的log功能。不过一般我们接触不到,NSLog就对它提供了高层次的封装,如 这篇文档 所提到的: 
You can use two interfaces in OS X to log messages: ASL and Syslog. You can also use a number of higher-level approaches such as NSLog. However, because most daemons are not linked against Foundation or the Application Kit, the low-level APIs are often more appropriate 
一些底层相关的守护进程(deamons)不会link如Foundation等高层框架,所以asl用在这儿正合适;而对于应用层的用NSLog。 
CocoaLumberjack 文档 中也说了NSLog效率低下的问题:
NSLog does 2 things: 
  • It writes log messages to the Apple System Logging (asl) facility. This allows log messages to show up in Console.app. 
  • It also checks to see if the application’s stderr stream is going to a terminal (such as when the application is being run via Xcode). If so it writes the log message to stderr (so that it shows up in the Xcode console).
To send a log message to the ASL facility, you basically open a client connection to the ASL daemon and send the message. BUT - each thread must use a separate client connection. So, to be thread safe, every time NSLog is called it opens a new asl client connection, sends the message, and then closes the connection. 
意识大概是说,NSLog会向ASL写log,同时向Terminal写log,而且同时会出现在 Console.app 中(Mac自带软件,用NSLog打出的log在其中全部可见);不仅如此,每一次NSLog都会新建一个ASL client并向ASL守护进程发起连接,log之后再关闭连接。所以说,当这个过程出现N次时,消耗大量资源导致程序变慢也就不奇怪了。 

时间和进程信息

主要原因已经找到,还有个值得注意的问题是 NSLog 每次会将当前的系统时间,进程和线程信息等作为前缀也打印出来,如: 
2012-34-56 12:34:56.789 XXXXXXXX[36818:303] xxxxxx
当然这些也可能是作为ASL的参数创建的,但不论如何,一定是有消耗的(虽然这个prefix十有八九不是我们需要的看到的) 

如何是好

NSLog有这样的消耗问题,那该怎么办呢?
  1. 拒绝残留的Log。现在项目都是多人共同开发,我们应该只把Log作为错误日志或者重要信息的日志使用,commit前请把自己调试的log去掉(尤其是在循环里写log的小伙伴,简直不能一起快乐的玩耍了)
  2. release版本中消除Log。debug归debug,再慢也不能波及到release版本,用预编译宏过滤下就好。
  3. 是时候换个Log系统了,如CocoaLumberjack,自建一个简单的当然也挺好(其实为了项目需要自己也写了个小log系统,实现可以按名字和级别显示log和一些扩展功能,以后有机会分享下)
不过个人认为debug时最好还是用调试器进行调试(尤其是只需要知道某个变量值的时候)

尝试使用断点+lldb调试器打Log

关于强大的 lldb 调试器用一个专题来讲都是应该,现在只了解一些皮毛,不过就算皮毛的功能也可以替代NSLog这种方法进行调试了,重要的一点是: 使用断点log不需要重新编译工程 ,况且和Xcode已经结合的很好,在此先只说打Log这件事。 

简单断点+po(p)

断点时可以在xcode的lldb调试区使用 po p 命令打印对象或变量,对于当前栈帧中引用到的变量都是可见的,所以说假如只是看一眼某个对象运行到这儿是不是存在,是什么值的话,设个断点就够了,况且IDE已经把这个功能集成,鼠标放变量上就可以了。 
lldb一些常用调试技巧可以这篇 入门教程

Condition和Action断点

断点不止能把程序断住,触发时也按一定条件,而且可以执行(一个或多个)Action,在断点上右键选择 Edit Breakpoint,弹出的断点设置中可以添加一些Action:
其中专门有一项就是 Log Message,做个小测试: 
for (int i = 0; i < 10; i++) {
    // break point here
}
设置断点后编辑断点: 
输入框下面就有支持的格式,表达式(或变量)可以使用 @exp@ 这种格式包起来。于是乎输出: 
break at: 'main()',  count: 4, sunnyxx says : 3
break at: 'main()',  count: 5, sunnyxx says : 4
break at: 'main()',  count: 6, sunnyxx says : 5
正如所料。
更多的调试技巧还需要深入研究,不过可以肯定的是,比起单纯的使用 NSLog,使用好的工具可以让我们debug的效率更高
解决方法
如果不想改变 NSLOG 输出就这样写
//如果release状态就不执行NSLog函数
#ifndef __OPTIMIZE__
#define NSLog(...) NSLog(__VA_ARGS__)
#else
# define NSLog(...) {}
#endif
自定义:
#ifdef DEBUG
#define HYLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define HYLog(...)
#endif

总结

  • NSLog耗费比较大的资源
  • NSLog被设计为error log,是ASL的高层封装
  • 在项目中避免提交commit自己的Debug log,release版本更要注意去除NSLog,可以使用自建的log系统或好用的log系统来替代NSLog
  • debug不应只局限于log满天飞,lldb断点调试是一个优秀的debug方法,需要再深入研究一下
blog.sunnyxx.com
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NSLog效率低下的原因以及NSLog宏定义 的相关文章

随机推荐

  • 计算机系统概述

    操作系统的概念 功能 操作系统 Operating System OS 是指控制和管理整个计算机系统的硬件和软件资源 并合理地组织调度计算机的工作和资源的分配 以提供用户和其他软件方便的接口和环境 它说计算机系统中最基本的系统软件 操作系统
  • anaconda添加python虚拟环境

    先将anaconda的源更换为国内源 否则下载很慢 conda config add channels https mirrors tuna tsinghua edu cn anaconda pkgs free conda config a
  • feigin应用

    feigin发送post请求 1 服务方一定加上 RequestBody RequestMapping value queryPerson ResponseBody public Person queryPerson RequestBody
  • 如何快速上手一款新的嵌入式CPU芯片(记录CC2540开发经历)

    新换了工作 需要熟悉新公司的产品开发项目 更新博客就懈怠了 不过环境的不同 也让我对嵌入式开发有了更深刻的理解 在原公司我主要负责在STM32F207芯片平台上 利用UCOS LWIP进行嵌入式服务器开发 工作涉及底层硬件 RTOS 协议栈
  • Linux内核源码分析-进程调度(三)-从进程创建到唤醒的过程去了解CFS调度器

    从进程创建到唤醒的过程去了解CFS调度器 从do fork开始 创建子进程 初始化新建进程p相关的调度参数 cfs的task fork操作 更新cfs rq上正在运行的进程的运行时间信息 更新cfs rq的最小虚拟运行时间 更新进程p对应的
  • redis学习笔记

    概述 redis可以当作缓存来使用 存在内存里 比读数据库更快 但是比从内存变量里取数据还是要慢不少的 redis sql 内存变量的对比 按存取速度来看 内存变量最快 sql最慢 但按照持久化的角度来说正好相反 各有优缺点 按需选择使用
  • 加法乘法原理、排列组合、线性规划

    排列组合 1 加法原理与乘法原理 加法原理 分类思想 一个事件的发生 分为几类事件的发生 通俗的说是好几种情况的发生 乘法原理 分步思想 一个事件的发生 分为几个子事件分步发生 这里要注意 1 子事件 如何把事件划分为几个子事件呢 子事件是
  • dlopen “no suitable image found ”问题之解决

    做一个练手小项目 基于 react transform boilerplate 的demo 克隆 react transform boilerplate项目 装包 package json中的包 style loader css loade
  • 并发、并行、同步、异步的概念

    并发与并行 假设一个工厂 包含多个车间 一个车间包含多个工人和多个房间 什么是cpu 工厂是时刻在运行的 因此可以理解cpu时刻在运行 什么cpu的核数 假设把一个cpu比作一份电量的话 一份电量又只能满足一个车间运行 那么其他车间就得停止
  • 使用python写一个星球大战游戏.py

    如果要使用 Python 写一个类似于星球大战的游戏 需要用到一些专业的游戏引擎 比如 Pygame 首先 需要安装 Pygame 库 可以使用以下命令进行安装 pipinstall pygame 其次 可以在 Pygame 中使用 pyt
  • 网络安全人才青黄不接、数字化转型迫在眉睫、你还在犹豫吗?

    大专能不能学网络安全呢 大专学网络安全能不能找到工作呢 大专学网络安全有竞争力吗 网络上关于质疑大专学历进入网络安全行业的声音越来越多了 居然有很多人在质疑大专学历从事网络安全没有竞争力 很多人看到某些招聘软件上起薪12K的薪资就望而却步了
  • Linux文件管理

    成功不易 加倍努力 1 文件系统目录结构 1 1文件系统的目录结构 1 2 常见的文件系统目录功能 1 3 应用程序的组成部分 1 4 Linux下的文件类型 2 文件操作命令 2 1 显示当前工作目录 2 2 绝对和相对路径 2 3 更改
  • Nano编辑器安装使用指南

    关于nano Nano编辑器是一个命令行文本编辑器 具有简单易用的界面和一些基本功能 Nano小巧友好 提供许多额外的特性 例如交互式的查找和替换 定位到指定的行列 自动缩进 特性切换 国际化支持 文件名标记完成等 Nano是为了代替闭源的
  • 《Zookeeper-分布式过程系统技术详解》第一部分基础概念笔记学习

    1 Zookeep的客户端API功能强大 其中包括 保障强一致性 有序性和持久性 实现通用的同步原语的能力 在实际分布式系统中 并发往往导致不正确的行为 ZooKeeper提供了一种简单的并发处理机制 2 ZooKeeper不适用的场景 整
  • 重新学javaweb---JSTL标签

    JSTL简介 标准标签库JSTL的全名为 Java Server Pages Standard Tag Library JSTL主要提供了5大类标签库 核心标签库 为日常任务提供通用支持 如显示和设置变量 重复使用一组项目 测试条件以及其他
  • Promise常用API介绍

    Promise中的API PromiseState 实例对象中的一个属性 Promisestate 状态 pending 未决定 resolved fullfilled 成功 rejected 失败 pending 变为resolved p
  • CSDN周赛64期题解(含部分代码)

    计算之魂 主题周赛如期回归 因为差不多每次都是新题 让人多了点期待 相信非编程题无需多言 答案都在书里 翻书翻得快 满分无障碍 当然 如果提前读过此书就更好了 比如原书中把金块切了 2 刀 问题中扩展了一下 变成切 9 刀 如果提前理解过原
  • 【推荐系统】 一、推荐系统简介

    1 推荐系统的作用和意义 在这个时代 无论信息消费者还是信息生产者都面临巨大的挑战 信息消费者 在大量信息中找到自己感兴趣的信息很困难 信息生产者 将自己生产的信息让广大消费者关注很困难 推荐系统将用户与信息联系起来 1 1 用户角度 推荐
  • Mysql语句执行顺序

    1 SQL书写顺序 select distinct 显示字段 from 表名 join 要连接的表名 on 连接查询条件 where 约束条件 group by 分组字段 having 分组过滤条件 order By DESC 降序 或AS
  • NSLog效率低下的原因以及NSLog宏定义

    我是前言 打Log是我们debug时最简单朴素的方法 NSLog 对于objc开发就像 printf 对于c一样重要 但在使用 NSLog 打印大量Log 尤其是在游戏开发时 如每一帧都打印数据 NSLog 会明显的拖慢程序的运行速度 游戏