声明:尊重原创,原文地址:带你打造一套 APM 监控系统。本文为根据原创文章整理所得,感谢浏览。

APM 是 Application Performance Monitoring 的缩写,监视和管理软件应用程序的性能和可用性。应用性能管理对一个应用的持续稳定运行至关重要。所以这篇文章就从一个 iOS App 的性能管理的纬度谈谈如何精确监控以及数据如何上报等技术点

App 的性能问题是影响用户体验的重要因素之一。性能问题主要包含:Crash、网络请求错误或者超时、UI 响应速度慢、主线程卡顿、CPU 和内存使用率高、耗电量大等等。大多数的问题原因在于开发者错误地使用了线程锁、系统函数、编程规范问题、数据结构等等。解决问题的关键在于尽早的发现和定位问题。

本篇文章着重总结了 APM 的原因以及如何收集数据。APM 数据收集后结合数据上报机制,按照一定策略上传数据到服务端。服务端消费这些信息并产出报告。请结合姊妹篇, 总结了如何打造一款灵活可配置、功能强大的数据上报组件。

一、卡顿监控

卡顿问题,就是在主线程上无法响应用户交互的问题。影响着用户的直接体验,所以针对 App 的卡顿监控是 APM 里面重要的一环。

FPS(frame per second)每秒钟的帧刷新次数,iPhone 手机以 60 为最佳,iPad 某些型号是 120,也是作为卡顿监控的一项参考参数,为什么说是参考参数?因为它不准确。先说说怎么获取到 FPS。CADisplayLink 是一个系统定时器,会以帧刷新频率一样的速率来刷新视图。 [CADisplayLink displayLinkWithTarget:self selector:@selector(###:)]。至于为什么不准我们来看看下面的示例代码

_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(p_displayLinkTick:)];
[_displayLink setPaused:YES];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

代码所示,CADisplayLink 对象是被添加到指定的 RunLoop 的某个 Mode 下。所以还是 CPU 层面的操作,卡顿的体验是整个图像渲染的结果:CPU + GPU。请继续往下看

1. 屏幕绘制原理

在这里插入图片描述

讲讲老式的 CRT 显示器的原理。 CRT 电子枪按照上面方式,从上到下一行行扫描,扫面完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;当一帧画面绘制完成后,电子枪恢复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(Vertical synchronization),简称 VSync。显示器通常以固定的频率进行刷新,这个固定的刷新频率就是 VSync 信号产生的频率。虽然现在的显示器基本都是液晶显示屏,但是原理保持不变。

在这里插入图片描述

通常,屏幕上一张画面的显示是由 CPU、GPU 和显示器是按照上图的方式协同工作的。CPU 根据工程师写的代码计算好需要现实的内容(比如视图创建、布局计算、图片解码、文本绘制等),然后把计算结果提交到 GPU,GPU 负责图层合成、纹理渲染,随后 GPU 将渲染结果提交到帧缓冲区。随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过数模转换传递给显示器显示。

在帧缓冲区只有一个的情况下,帧缓冲区的读取和刷新都存在效率问题,为了解决效率问题,显示系统会引入2个缓冲区,即双缓冲机制。在这种情况下,GPU 会预先渲染好一帧放入帧缓冲区,让视频控制器来读取,当下一帧渲染好后,GPU 直接把视频控制器的指针指向第二个缓冲区。提升了效率。

目前来看,双缓冲区提高了效率,但是带来了新的问题:当视频控制器还未读取完成时,即屏幕内容显示了部分,GPU 将新渲染好的一帧提交到另一个帧缓冲区并把视频控制器的指针指向新的帧缓冲区,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂的情况。

为了解决这个问题,GPU 通常有一个机制叫垂直同步信号(V-Sync),当开启垂直同步信号后,GPU 会等到视频控制器发送 V-Sync 信号后,才进行新的一帧的渲染和帧缓冲区的更新。这样的几个机制解决了画面撕裂的情况,也增加了画面流畅度。但需要更多的计算资源

在这里插入图片描述

答疑

可能有些人会看到「当开启垂直同步信号后,GPU 会等到视频控制器发送 V-Sync 信号后,才进行新的一帧的渲染和帧缓冲区的更新」这里会想,GPU 收到 V-Sync 才进行新的一帧渲染和帧缓冲区的更新,那是不是双缓冲区就失去意义了?

设想一个显示器显示第一帧图像和第二帧图像的过程。首先在双缓冲区的情况下,GPU 首先渲染好一帧图像存入到帧缓冲区,然后让视频控制器的指针直接直接这个缓冲区,显示第一帧图像。第一帧图像的内容显示完成后,视频控制器发送 V-Sync 信号,GPU 收到 V-Sync 信号后渲染第二帧图像并将视频控制器的指针指向第二个帧缓冲区。

看上去第二帧图像是在等第一帧显示后的视频控制器发送 V-Sync 信号。是吗?真是这样的吗? 😭 想啥呢,当然不是。 🐷 不然双缓冲区就没有存在的意义了

揭秘。请看下图:

在这里插入图片描述

当第一次 V-Sync 信号到来时,先渲染好一帧图像放到帧缓冲区,但是不展示,当收到第二个 V-Sync 信号后读取第一次渲染好的结果(视频控制器的指针指向第一个帧缓冲区),并同时渲染新的一帧图像并将结果存入第二个帧缓冲区,等收到第三个 V-Sync 信号后,读取第二个帧缓冲区的内容(视频控制器的指针指向第二个帧缓冲区),并开始第三帧图像的渲染并送入第一个帧缓冲区,依次不断循环往复。

请查看资料,需要梯子:Multiple buffering

2. 卡顿产生的原因

在这里插入图片描述

VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容(视图创建、布局计算、图片解码、文本绘制等)。然后将计算的内容提交到 GPU,GPU 经过图层的变换、合成、渲染,随后 GPU 把渲染结果提交到帧缓冲区,等待下一次 VSync 信号到来再显示之前渲染好的结果。在垂直同步机制的情况下,如果在一个 VSync 时间周期内,CPU 或者 GPU 没有完成内容的提交,就会造成该帧的丢弃,等待下一次机会再显示,这时候屏幕上还是之前渲染的图像,所以这就是 CPU、GPU 层面界面卡顿的原因。

目前 iOS 设备有双缓存机制,也有三缓冲机制,Android 现在主流是三缓冲机制,在早期是单缓冲机制。
iOS 三缓冲机制例子

CPU 和 GPU 资源消耗原因很多,比如对象的频繁创建、属性调整、文件读取、视图层级的调整、布局的计算(AutoLayout 视图个数多了就是线性方程求解难度变大)、图片解码(大图的读取优化)、图像绘制、文本渲染、数据库读取(多读还是多写乐观锁、悲观锁的场景)、锁的使用(举例:自旋锁使用不当会浪费 CPU)等方面。开发者根据自身经验寻找最优解(这里不是本文重点)。

3. APM 如何监控卡顿并上报

CADisplayLink 肯定不用了,这个 FPS 仅作为参考。一般来讲,卡顿的监测有2种方案:监听 RunLoop 状态回调、子线程 ping 主线程

3.1 RunLoop 状态监听的方式

RunLoop 负责监听输入源进行调度处理。比如网络、输入设备、周期性或者延迟事件、异步回调等。RunLoop 会接收2种类型的输入源:一种是来自另一个线程或者来自不同应用的异步消息(source0事件)、另一种是来自预定或者重复间隔的事件。

RunLoop 状态如下图:

RunLoop

第一步:通知 Observers,RunLoop 要开始进入 loop,紧接着进入 loop。

if (currentMode->_observerMask & kCFRunLoopEntry )// 通知 Observers: RunLoop 即将进入 loop__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 进入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

第二步:开启 do while 循环保活线程,通知 Observers,RunLoop 触发 Timer 回调、Source0 回调,接着执行被加入的 block。

 if (rlm->_observerMask & kCFRunLoopBeforeTimers)//  通知 Observers: RunLoop 即将触发 Timer 回调__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources)//  通知 Observers: RunLoop 即将触发 Source 回调__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);

第三步:RunLoop 在触发 Source0 回调后,如果 Source1 是 ready 状态,就会跳转到 handle_msg 去处理消息。

//  如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINImsg = (mach_msg_header_t *)msg_buffer;if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {goto handle_msg;}
#elif DEPLOYMENT_TARGET_WINDOWSif (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {goto handle_msg;}
#endif
}

第四步:回调触发后,通知 Observers 即将进入休眠状态。

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);__CFRunLoopSetSleeping(rl);

第五步:进入休眠后,会等待 mach_port 消息,以便再次唤醒。只有以下4种情况才可以被再次唤醒。

  • 基于 port 的 source 事件
  • Timer 时间到
  • RunLoop 超时
  • 被调用者唤醒
do {if (kCFUseCollectableAllocator) {// objc_clear_stack(0);// <rdar://problem/16393959>memset(msg_buffer, 0, sizeof(msg_buffer));}msg = (mach_msg_header_t *)msg_buffer;__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));if (rlm->_timerFired) {// Leave livePort as the queue port, and service timers belowrlm->_timerFired = false;break;} else {if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);}} else {// Go ahead and leave the inner loop.break;}
} while (1);

第六步:唤醒时通知 Observer,RunLoop 的线程刚刚被唤醒了。

// 通知 Observers: RunLoop 的线程刚刚被唤醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);// 处理消息handle_msg:;__CFRunLoopSetIgnoreWakeUps(rl);

第七步:RunLoop 唤醒后,处理唤醒时收到的消息:

  • 如果是 Timer 时间到,则触发 Timer 的回调
  • 如果是 dispatch,则执行 block
  • 如果是 source1 事件,则处理这个事件
#if USE_MK_TIMER_TOO// 如果一个 Timer 到时间了,触发这个Timer的回调else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {CFRUNLOOP_WAKEUP_FOR_TIMER();// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer__CFArmNextTimerInMode(rlm, rl);}}
#endif//  如果有dispatch到main_queue的block,执行blockelse if (livePort == dispatchPort) {CFRUNLOOP_WAKEUP_FOR_DISPATCH();__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWSvoid *msg = 0;
#endif__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);__CFRunLoopLock(rl);__CFRunLoopModeLock(rlm);sourceHandledThisLoop = true;didDispatchPortLastTime = true;}// 如果一个 Source1 (基于port) 发出事件了,处理这个事件else {CFRUNLOOP_WAKEUP_FOR_SOURCE();// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINImach_msg_header_t *reply = NULL;sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;if (NULL != reply) {(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);}
#elif DEPLOYMENT_TARGET_WINDOWSsourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif

第八步:根据当前 RunLoop 状态判断是否需要进入下一个 loop。当被外部强制停止或者 loop 超时,就不继续下一个 loop,否则进入下一个 loop。

if (sourceHandledThisLoop && stopAfterHandle) {// 进入loop时参数说处理完事件就返回retVal = kCFRunLoopRunHandledSource;} else if (timeout_context->termTSR < mach_absolute_time()) {// 超出传入参数标记的超时时间了retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {__CFRunLoopUnsetStopped(rl);// 被外部调用者强制停止了retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {rlm->_stopped = false;retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {// source/timer一个都没有retVal = kCFRunLoopRunFinished;
}

完整且带有注释的 RunLoop 代码见此处。 Source1 是 RunLoop 用来处理 Mach port 传来的系统事件的,Source0 是用来处理用户事件的。收到 Source1 的系统事件后本质还是调用 Source0 事件的处理函数。

在这里插入图片描述

RunLoop 6个状态


typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry ,           // 进入 loopkCFRunLoopBeforeTimers ,    // 触发 Timer 回调kCFRunLoopBeforeSources ,   // 触发 Source0 回调kCFRunLoopBeforeWaiting ,   // 等待 mach_port 消息kCFRunLoopAfterWaiting ),   // 接收 mach_port 消息kCFRunLoopExit ,            // 退出 loopkCFRunLoopAllActivities     // loop 所有状态改变
}

RunLoop 在进入睡眠前的方法执行时间过长而导致无法进入睡眠,或者线程唤醒后接收消息时间过长而无法进入下一步,都会阻塞线程。如果是主线程,则表现为卡顿。

一旦发现进入睡眠前的 KCFRunLoopBeforeSources 状态,或者唤醒后 KCFRunLoopAfterWaiting,在设置的时间阈值内没有变化,则可判断为卡顿,此时 dump 堆栈信息,还原案发现场,进而解决卡顿问题。

开启一个子线程,不断进行循环监测是否卡顿了。在 n 次都超过卡顿阈值后则认为卡顿了。卡顿之后进行堆栈 dump 并上报(具有一定的机制,数据处理在下一 part 讲)。

WatchDog 在不同状态下具有不同的值:

  • 启动(Launch):20s
  • 恢复(Resume):10s
  • 挂起(Suspend):10s
  • 退出(Quit):6s
  • 后台(Background):3min(在 iOS7 之前可以申请 10min;之后改为 3min;可连续申请,最多到 10min)

卡顿阈值的设置的依据是 WatchDog 的机制。APM 系统里面的阈值需要小于 WatchDog 的值,所以取值范围在 [1, 6] 之间,业界通常选择3秒。

通过 long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 方法判断是否阻塞主线程,Returns zero on success, or non-zero if the timeout occurred. 返回非0则代表超时阻塞了主线程。

在这里插入图片描述

可能很多人纳闷 RunLoop 状态那么多,为什么选择 KCFRunLoopBeforeSources 和 KCFRunLoopAfterWaiting?因为大部分卡顿都是在 KCFRunLoopBeforeSources 和 KCFRunLoopAfterWaiting 之间。比如 Source0 类型的 App 内部事件等

Runloop 检测卡顿流程图如下:

在这里插入图片描述

关键代码如下:

// 设置Runloop observer的运行环境
CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL};
// 创建Runloop observer对象
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
// 将新建的observer加入到当前thread的runloop
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 创建信号
_semaphore = dispatch_semaphore_create(0);__weak __typeof(self) weakSelf = self;
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{__strong __typeof(weakSelf) strongSelf = weakSelf;if (!strongSelf) {return;}while (YES) {if (strongSelf.isCancel) {return;}// N次卡顿超过阈值T记录为一次卡顿long semaphoreWait = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, strongSelf.limitMillisecond * NSEC_PER_MSEC));if (semaphoreWait != 0) {if (self->_activity == kCFRunLoopBeforeSources || self->_activity == kCFRunLoopAfterWaiting) {if (++strongSelf.countTime < strongSelf.standstillCount){continue;}// 堆栈信息 dump 并结合数据上报机制,按照一定策略上传数据到服务器。堆栈 dump 会在下面讲解。数据上报会在 [打造功能强大、灵活可配置的数据上报组件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md) 讲}}strongSelf.countTime = 0;}
});

3.2 子线程 ping 主线程监听的方式

开启一个子线程,创建一个初始值为0的信号量、一个初始值为 YES 的布尔值类型标志位。将设置标志位为 NO 的任务派发到主线程中去,子线程休眠阈值时间,时间到后判断标志位是否被主线程成功(值为 NO),如果没成功则认为主线程发生了卡顿情况,此时 dump 堆栈信息并结合数据上报机制,按照一定策略上传数据到服务器。数据上报会在 打造功能强大、灵活可配置的数据上报组件 讲

while (self.isCancelled == NO) {@autoreleasepool {__block BOOL isMainThreadNoRespond = YES;dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);dispatch_async(dispatch_get_main_queue(), ^{isMainThreadNoRespond = NO;dispatch_semaphore_signal(semaphore);});[NSThread sleepForTimeInterval:self.threshold];if (isMainThreadNoRespond) {if (self.handlerBlock) {self.handlerBlock(); // 外部在 block 内部 dump 堆栈(下面会讲),数据上报}}dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);}}

4. 堆栈 dump

方法堆栈的获取是一个麻烦事。理一下思路。[NSThread callStackSymbols] 可以获取当前线程的调用栈。但是当监控到卡顿发生,需要拿到主线程的堆栈信息就无能为力了。从任何线程回到主线程这条路走不通。先做个知识回顾。

在计算机科学中,调用堆栈是一种栈类型的数据结构,用于存储有关计算机程序的线程信息。这种栈也叫做执行堆栈、程序堆栈、控制堆栈、运行时堆栈、机器堆栈等。调用堆栈用于跟踪每个活动的子例程在完成执行后应该返回控制的点。

维基百科搜索到 “Call Stack” 的一张图和例子,如下:

在这里插入图片描述

上图表示为一个栈。分为若干个栈帧(Frame),每个栈帧对应一个函数调用。下面蓝色部分表示 DrawSquare 函数,它在执行的过程中调用了 DrawLine 函数,用绿色部分表示。

可以看到栈帧由三部分组成:函数参数、返回地址、局部变量。比如在 DrawSquare 内部调用了 DrawLine 函数:第一先把 DrawLine 函数需要的参数入栈;第二把返回地址(控制信息。举例:函数 A 内调用函数 B,调用函数B 的下一行代码的地址就是返回地址)入栈;第三函数内部的局部变量也在该栈中存储。

栈指针 Stack Pointer 表示当前栈的顶部,大多部分操作系统都是栈向下生长,所以栈指针是最小值。帧指针 Frame Pointer 指向的地址中,存储了上一次 Stack Pointer 的值,也就是返回地址。

大多数操作系统中,每个栈帧还保存了上一个栈帧的帧指针。因此知道当前栈帧的 Stack Pointer 和 Frame Pointer 就可以不断回溯,递归获取栈底的帧。

接下来的步骤就是拿到所有线程的 Stack Pointer 和 Frame Pointer。然后不断回溯,还原案发现场。

5. Mach Task 知识

Mach task:

App 在运行的时候,会对应一个 Mach Task,而 Task 下可能有多条线程同时执行任务。《OS X and iOS Kernel Programming》 中描述 Mach Task 为:任务(Task)是一种容器对象,虚拟内存空间和其他资源都是通过这个容器对象管理的,这些资源包括设备和其他句柄。简单概括为:Mack task 是一个机器无关的 thread 的执行环境抽象。

作用: task 可以理解为一个进程,包含它的线程列表。

结构体:task_threads,将 target_task 任务下的所有线程保存在 act_list 数组中,数组个数为 act_listCnt

kern_return_t task_threads
(task_t traget_task,thread_act_array_t *act_list,                     //线程指针列表mach_msg_type_number_t *act_listCnt  //线程个数
)

thread_info:

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
);

如何获取线程的堆栈数据:

系统方法 kern_return_t task_threads(task_inspect_t target_task, thread_act_array_t *act_list, mach_msg_type_number_t *act_listCnt); 可以获取到所有的线程,不过这种方法获取到的线程信息是最底层的 mach 线程

对于每个线程,可以用 kern_return_t thread_get_state(thread_act_t target_act, thread_state_flavor_t flavor, thread_state_t old_state, mach_msg_type_number_t *old_stateCnt); 方法获取它的所有信息,信息填充在 _STRUCT_MCONTEXT 类型的参数中,这个方法中有2个参数随着 CPU 架构不同而不同。所以需要定义宏屏蔽不同 CPU 之间的区别。

_STRUCT_MCONTEXT 结构体中,存储了当前线程的 Stack Pointer 和最顶部栈帧的 Frame pointer,进而回溯整个线程调用堆栈。

但是上述方法拿到的是内核线程,我们需要的信息是 NSThread,所以需要将内核线程转换为 NSThread。

pthread 的 p 是 POSIX 的缩写,表示「可移植操作系统接口」(Portable Operating System Interface)。设计初衷是每个系统都有自己独特的线程模型,且不同系统对于线程操作的 API 都不一样。所以 POSIX 的目的就是提供抽象的 pthread 以及相关 API。这些 API 在不同的操作系统中有不同的实现,但是完成的功能一致。

Unix 系统提供的 task_threadsthread_get_state 操作的都是内核系统,每个内核线程由 thread_t 类型的 id 唯一标识。pthread 的唯一标识是 pthread_t 类型。其中内核线程和 pthread 的转换(即 thread_t 和 pthread_t)很容易,因为 pthread 设计初衷就是「抽象内核线程」。

memorystatus_action_neededpthread_create 方法创建线程的回调函数为 nsthreadLauncher

static void *nsthreadLauncher(void* thread)  
{NSThread *t = (NSThread*)thread;[nc postNotificationName: NSThreadDidStartNotification object:t userInfo: nil];[t _setName: [t name]];[t main];[NSThread exit];return NULL;
}

NSThreadDidStartNotification 其实就是字符串 @"_NSThreadDidStartNotification"。

<NSThread: 0x...>{number = 1, name = main}  

为了 NSThread 和内核线程对应起来,只能通过 name 一一对应。 pthread 的 API pthread_getname_np 也可获取内核线程名字。np 代表 not POSIX,所以不能跨平台使用。

思路概括为:将 NSThread 的原始名字存储起来,再将名字改为某个随机数(时间戳),然后遍历内核线程 pthread 的名字,名字匹配则 NSThread 和内核线程对应了起来。找到后将线程的名字还原成原本的名字。对于主线程,由于不能使用 pthread_getname_np,所以在当前代码的 load 方法中获取到 thread_t,然后匹配名字。

static mach_port_t main_thread_id;  
+ (void)load {main_thread_id = mach_thread_self();
}

二、 App 启动时间监控

1. App 启动时间的监控

应用启动时间是影响用户体验的重要因素之一,所以我们需要量化去衡量一个 App 的启动速度到底有多快。启动分为冷启动和热启动。

在这里插入图片描述

冷启动:App 尚未运行,必须加载并构建整个应用。完成应用的初始化。冷启动存在较大优化空间。冷启动时间从 application: didFinishLaunchingWithOptions: 方法开始计算,App 一般在这里进行各种 SDK 和 App 的基础初始化工作。

热启动:应用已经在后台运行(常见场景:比如用户使用 App 过程中点击 Home 键,再打开 App),由于某些事件将应用唤醒到前台,App 会在 applicationWillEnterForeground: 方法接受应用进入前台的事件

思路比较简单。如下:

  • 在监控类的 load 方法中先拿到当前的时间值
  • 监听 App 启动完成后的通知 UIApplicationDidFinishLaunchingNotification
  • 收到通知后拿到当前的时间
  • 步骤1和3的时间差就是 App 启动时间。

mach_absolute_time 是一个 CPU/总线依赖函数,返回一个 CPU 时钟周期数。系统休眠时不会增加。是一个纳秒级别的数字。获取前后2个纳秒后需要转换到秒。需要基于系统时间的基准,通过 mach_timebase_info 获得。

mach_timebase_info_data_t g_cmmStartupMonitorTimebaseInfoData = 0;
mach_timebase_info(&g_cmmStartupMonitorTimebaseInfoData);
uint64_t timelapse = mach_absolute_time() - g_cmmLoadTime;
double timeSpan = (timelapse * g_cmmStartupMonitorTimebaseInfoData.numer) / (g_cmmStartupMonitorTimebaseInfoData.denom * 1e9);

2. 线上监控启动时间就好,但是在开发阶段需要对启动时间做优化。

要优化启动时间,就先得知道在启动阶段到底做了什么事情,针对现状作出方案。

pre-main 阶段定义为 App 开始启动到系统调用 main 函数这个阶段;main 阶段定义为 main 函数入口到主 UI 框架的 viewDidAppear。

App 启动过程:

  • 解析 Info.plist:加载相关信息例如闪屏;沙盒建立、权限检查;
  • Mach-O 加载:如果是胖二进制文件,寻找合适当前 CPU 架构的部分;加载所有依赖的 Mach-O 文件(递归调用 Mach-O 加载的方法);定义内部、外部指针引用,例如字符串、函数等;加载分类中的方法;c++ 静态对象加载、调用 Objc 的 +load() 函数;执行声明为 _attribute((constructor)) 的 c 函数;
  • 程序执行:调用 main();调用 UIApplicationMain();调用 applicationWillFinishLaunching();

Pre-Main 阶段

在这里插入图片描述

Main 阶段

在这里插入图片描述

2.1 加载 Dylib

每个动态库的加载,dyld 需要:

  • 分析所依赖的动态库
  • 找到动态库的 Mach-O 文件
  • 打开文件
  • 验证文件
  • 在系统核心注册文件签名
  • 对动态库的每一个 segment 调用 mmap()

优化:

  • 减少非系统库的依赖
  • 使用静态库而不是动态库
  • 合并非系统动态库为一个动态库

2.2 Rebase && Binding

优化:

  • 减少 Objc 类数量,减少 selector 数量,把未使用的类和函数都可以删掉
  • 减少 c++ 虚函数数量
  • 转而使用 Swift struct(本质就是减少符号的数量)

2.3 Initializers

优化:

  • 使用 +initialize 代替 +load
  • 不要使用过 attribute*((constructor)) 将方法显示标记为初始化器,而是让初始化方法调用时才执行。比如使用 dispatch_one、pthread_once() 或 std::once()。也就是第一次使用时才初始化,推迟了一部分工作耗时也尽量不要使用 c++ 的静态对象

2.4 pre-main 阶段影响因素

  • 动态库加载越多,启动越慢。
  • ObjC 类越多,函数越多,启动越慢。
  • 可执行文件越大启动越慢。
  • C 的 constructor 函数越多,启动越慢。
  • C++ 静态对象越多,启动越慢。
  • ObjC 的 +load 越多,启动越慢。

优化手段:

  • 减少依赖不必要的库,不管是动态库还是静态库;如果可以的话,把动态库改造成静态库;如果必须依赖动态库,则把多个非系统的动态库合并成一个动态库
  • 检查下 framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
  • 合并或者删减一些OC类和函数。关于清理项目中没用到的类,使用工具AppCode代码检查功能,查到当前项目中没有用到的类(也可以用根据linkmap文件来分析,但是准确度不算很高)有一个叫做FUI的开源项目能很好的分析出不再使用的类,准确率非常高,唯一的问题是它处理不了动态库和静态库里提供的类,也处理不了C++的类模板
  • 删减一些无用的静态变量
  • 删减没有被调用到或者已经废弃的方法
  • 将不必须在 +load 方法中做的事情延迟到 +initialize中,尽量不要用 C++ 虚函数(创建虚函数表有开销)
  • 类和方法名不要太长:iOS每个类和方法名都在 __cstring 段里都存了相应的字符串值,所以类和方法名的长短也是对可执行文件大小是有影响的,还是 Object-c 的动态特性,因为需要通过类/方法名反射找到这个类/方法进行调用,Object-c 对象模型会把类/方法名字符串都保存下来;
  • 用 dispatch_once() 代替所有的 attribute((constructor)) 函数、C++ 静态对象初始化、ObjC 的 +load 函数;
  • 在设计师可接受的范围内压缩图片的大小,会有意外收获。
    压缩图片为什么能加快启动速度呢?因为启动的时候大大小小的图片加载个十来二十个是很正常的,图片小了,IO操作量就小了,启动当然就会快了,比较靠谱的压缩算法是 TinyPNG。

2.5 main 阶段优化

  • 减少启动初始化的流程。能懒加载就懒加载,能放后台初始化就放后台初始化,能延迟初始化的就延迟初始化,不要卡主线程的启动时间,已经下线的业务代码直接删除
  • 优化代码逻辑。去除一些非必要的逻辑和代码,减小每个流程所消耗的时间
  • 启动阶段使用多线程来进行初始化,把 CPU 性能发挥最大
  • 使用纯代码而不是 xib 或者 storyboard 来描述 UI,尤其是主 UI 框架,比如 TabBarController。因为 xib 和 storyboard 还是需要解析成代码来渲染页面,多了一步。
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. mysql if case when then函数的使用

    IF()函数的使用IF(expr1,expr2,expr3),如果expr1的值为true,则返回expr2的值,如果expr1的值为false,则返回expr3的值。SELECT IF(TRUE,A,B); -- 输出结果:A SELECT IF(FALSE,A,B); -- 输出结果:BIFNULL()函数的使用IFNULL(expr1,expr2),如果expr1的值为null,则返回…...

    2024/4/27 9:17:09
  2. centOS系统的备份与还原

    centOS系统的备份与还原第一章、准备工作一、镜像备份与还原二、全文件备份与还原第二章、centos网络配置第三章、硬盘镜像(实操OK)一、备份二、还原第四章、分区镜像(未验证)一、备份二、还原第五章、全文件备份与还原(在其他的机器上还原失败了,在原备份机器上还原不知…...

    2024/4/28 14:45:28
  3. 如何维护换IP代理软件,保障爬虫程序高效运行?

    使用爬虫IP代理的最好实施方案是在本地网维护一个IP池,这样能够更合理的确保网络爬虫工作上的高效稳定持久的运作,那麼怎样在本地维护IP池呢? 一、在代理服务商认可的调用API频率下尽可能多的提取IP,之后写一个检测程序,连续不断的去用这些代理访问一个稳定的网站,看是否…...

    2024/4/24 7:22:25
  4. TCP学习总结

    目录TCP报文段的首部格式TCP报文段的首部格式源端口个目标端口:各占两个字节,分别写入源端口号和目标端口号。序号:占4字节。在一个TCP连接中传送的字节流中的每一个字节都是按照顺序编号。确认号:占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。若确认号=N,…...

    2024/4/9 18:55:59
  5. ECharts 折线图使用教程

    1 Series说明:系列列表。1.1 series.lineStyle说明:线条样式。1.1.1 series.lineStyle.color说明:线条颜色。默认值:"#000"1.1.2 series.lineStyle.width说明:线条宽度。默认值:2。1.1.3 series.lineStyle.type说明:线条类型。默认值:solid。可选值:(1) sol…...

    2024/4/29 10:56:21
  6. ant build.xml文件将使用到的属性放到一个额外的build.properties文件里

    build.xml:<?xml version="1.0"?><project name="Hello World Project" default="info"><property file="build.properties"/><target name="info"><echo>Apache Ant version is ${ant.ver…...

    2024/4/9 18:55:57
  7. Thymeleaf模板引擎学习

    开发传统Java WEB项目时,我们可以使用JSP页面模板语言,但是在SpringBoot中已经不推荐使用JSP页面进行页面渲染了。从而Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP,或其他模板引擎,如Velocity、FreeMarker等。它的语…...

    2024/4/29 13:28:15
  8. 3306端口占用问题+以管理员身份运行cmd

    安装新版mysql时发现3306端口被占用,按照csdn前人的经验曾是了几种方法,行为有效的方法如下: 1.打开cmd 2.输入C跳转到C盘,然后输入:system32 3.输入:netstat -aon(中间含有空格) 4.找到3306对应的进程然后输入taskkill /pid xxx -t -f(信息相对应的是进程号,比如53…...

    2024/4/18 9:09:45
  9. 阿里Java学习路线:阶段 1:Java语言基础-Java面向对象编程:第27章:异常的捕获及处理:课时123:RuntimeException

    通过之前的分析可以发现只要方法后面带有throws往往都是告诉用户本方法可能产生的异常是什么,所以这个时候来观察一段代码。 public class JavaDemo {public static void main(String args[]) {int num = Integer.parseInt("123");System.out.println(num);} }打开I…...

    2024/4/21 20:45:18
  10. [02]JavaSE基础部分:运算符与表达式

    运算符操纵数据:运算符操作数据的式子就是表达式 所有的运算符都能根据自己的运算对象生成一个值 运算符的副作用:运算符改变了运算对象的值:例如++、--运算符 几乎所有的运算符都只能操纵基本类型 但 =、 == 、!= 可以操作引用类型 在字符串类String中可以使用运算符+、+=表…...

    2024/4/30 16:05:24
  11. 怎么防止页面重复提交?

    幂等性 无论请求发送多少次,请求的结果都是一样的 1.向后台发送一次请求,生成一个token 2.将token带到前端页面上(客户端)的Form表单中,使用隐藏域来存储这个token,表单提交时连同这个token一起提交到服务器 3.服务端生成的token存储在session中,将客户端提交的token与s…...

    2024/4/25 23:27:20
  12. Ubuntu20.04 Openssl安装

    安装背景 因为ubuntu20.04LTS中自带的openssl1.1.1版本的函数库不天汇使用,在编译过程中无法找到编译路径,故此手动安装openssl(我是个懒人,不到万不得已,不会手动安装,直接apt-get不香吗?) 安装步骤在官网下载函数代码包,openssl官网地址 下载后原地解压即可:tar zx…...

    2024/4/28 16:17:23
  13. nginx location语法

    匹配模式= 全等匹配~ 正则匹配! 非 (!= !~)* 忽略大小写 (~* !~*)^~ 优先匹配优先级:全等匹配 > 优先匹配 > 正则匹配root vs alias访问url:/static/thisisa.jslocation /static/{root /var/projectAalias /var/projectB}root:/var/projectA/static/thisisa.js (后面加…...

    2024/4/23 11:21:56
  14. [C++] PAT ZigZagging on a Tree (30分)

    Sample Input:8 12 11 20 17 1 15 8 5 12 20 17 11 15 8 5 1Sample Output:1 11 5 8 17 12 20 15题解: 1.先用中序-后序构建二叉树, 2.设立stack<node*> odd,表示将奇数层结点的孩子结点放入odd中,先放右孩子,再放左孩子 3.设立stack<node*> even,表示将偶数层结…...

    2024/4/12 15:12:42
  15. SSM框架整合Redis

    一,引入jar包<!-- jedis依赖包 --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency><!-- redis数据依赖包 --><dependency><groupI…...

    2024/5/1 0:48:06
  16. Nginx配置详解

    Nginx配置详解序言Nginx是lgor Sysoev为俄罗斯访问量第二的rambler.ru站点设计开发的。从2004年发布至今,凭借开源的力量,已经接近成熟与完善。Nginx功能丰富,可作为HTTP服务器,也可作为反向代理服务器,邮件服务器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功…...

    2024/4/12 15:12:32
  17. HTML中选择器详述

    选择器 选择器:浏览器通过选择器为满足条件的HTML元素添加CSS样式。常用类型有六类:标签选择器,类选择器,id选择器,后代选择器,分组选择器和通配符选择器。 标签选择器 标签选择器:浏览器为HTML文档内标签名与标签选择器名相同的标签元素添加CSS样式,其语法如下: 标签选…...

    2024/4/30 7:43:57
  18. java进阶--try...catch中的finally子句、面试题笔试题

    1. finally 通常在finally语句块中完成资源的关闭/释放,因为即使try中出现了异常finally中的代码一定会执行。 下列程序空指针异常后会跳过流的关闭代码,执行catch中的语句,所以该程序不安全,关闭不了流: public static void main(String[] args) {try {//创建输入流对象F…...

    2024/4/24 23:57:46
  19. H5新增和表单-7.13

    HTML5 2014年10月29日,万维网联盟宣布,经过接近8年的艰苦努力,该标准规范终于制定完成HTML5,它是HTML4.0升级版,并不仅仅只是做为HTML标记语言的一个最新版本,更重要的是它制定了Web应用开发的一系列标准,成为第一个将Web做为应用开发平台的HTML语言。与传统的技术相比,…...

    2024/4/30 18:57:47
  20. powershell 脚本运行策略,参数....

    1.运行策略Powershell一般初始化情况下都会禁止脚本执行。脚本能否执行取决于Powershell的执行策略。PS E:> Get-ExecutionPolicy Restricted PS E:> Set-ExecutionPolicy UnRestricted2.直接运行PS E:> "Hello,Powershell Script" > MyScript.ps1 PS E:…...

    2024/4/24 5:06:05

最新文章

  1. LabVIEW飞机机电系统综合测试平台

    LabVIEW飞机机电系统综合测试平台 在现代航空领域&#xff0c;机电系统的准确性与可靠性对飞行安全至关重要。针对飞机机电管理计算机&#xff08;UMC&#xff09;复杂度增加、测试覆盖率低、效率不高等问题&#xff0c;开发了一套基于LabVIEW的机电系统综合测试平台。平台通过…...

    2024/5/1 13:11:39
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 动态规划刷题(算法竞赛、蓝桥杯)--饥饿的奶牛(线性DP)

    1、题目链接&#xff1a;饥饿的奶牛 - 洛谷 #include <bits/stdc.h> using namespace std; const int N3000010; vector<int> a[N];//可变数组vector存区间 int n,mx,f[N]; int main(){scanf("%d",&n);for(int i1;i<n;i){int x,y;scanf("%…...

    2024/4/30 1:55:44
  4. 小核引导RTOS

    参考 参考1. How does FSBL load the FreeRTOS on the small core and execute it?参考2. Duo now supports big and little cores?Come and play!Milk-V Duo, start&#xff01; 日志 FSBL Jb2829:g362832ac6-dirty:2024-04-02T13:31:1100:00 # 版本信息 st_on_reason…...

    2024/5/1 4:39:19
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/1 10:25:26
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/4/30 0:57:52
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/4/29 18:43:42
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/1 4:07:45
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/4/30 23:32:22
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/4/30 23:16:16
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/1 6:35:25
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/1 11:24:00
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/1 4:35:02
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/4/30 14:53:47
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/4/30 22:14:26
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/1 6:34:45
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/4/30 22:57:18
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/4/30 20:39:53
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/1 4:45:02
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/1 8:32:56
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/4/29 3:42:58
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/1 11:51:23
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/1 5:23:20
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/4/30 20:52:33
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57