【Android 内存优化】KOOM 快手开源框架线上内存监控方案-源码剖析

文章目录

  • 前言
  • OOMMonitorInitTask.INSTANCE.init
  • OOMMonitor.INSTANCE.startLoop
  • super.startLoop
    • call() == LoopState.Terminate
  • dumpAndAnalysis
  • dump
  • startAnalysisService
  • 回到startLoop方法
  • 总结

前言

这篇文章主要剖析KOOM的Java层源码设计逻辑。

使用篇请看上一篇:
【Android KOOM】KOOM java leak使用全解析

OOMMonitorInitTask.INSTANCE.init

OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication());

这里进行初始化,来看看init里面做了什么:

object OOMMonitorInitTask : InitTask {override fun init(application: Application) {val config = OOMMonitorConfig.Builder().setThreadThreshold(50) //50 only for test! Please use default value!.setFdThreshold(300) // 300 only for test! Please use default value!.setHeapThreshold(0.9f) // 0.9f for test! Please use default value!.setVssSizeThreshold(1_000_000) // 1_000_000 for test! Please use default value!.setMaxOverThresholdCount(1) // 1 for test! Please use default value!.setAnalysisMaxTimesPerVersion(3) // Consider use default value!.setAnalysisPeriodPerVersion(15 * 24 * 60 * 60 * 1000) // Consider use default value!.setLoopInterval(5_000) // 5_000 for test! Please use default value!.setEnableHprofDumpAnalysis(true).setHprofUploader(object : OOMHprofUploader {override fun upload(file: File, type: OOMHprofUploader.HprofType) {MonitorLog.e("OOMMonitor", "todo, upload hprof ${file.name} if necessary")}}).setReportUploader(object : OOMReportUploader {override fun upload(file: File, content: String) {MonitorLog.i("OOMMonitor", content)MonitorLog.e("OOMMonitor", "todo, upload report ${file.name} if necessary")}}).build()MonitorManager.addMonitorConfig(config)}
}

可以看到里面做了各种参数的配置,包括上传hprof和报告的上传回调。
使用了构建者模式来进行参数设置,接着通过MonitorManager.addMonitorConfig(config) 添加到MonitorManager中,可见MonitorManager这个类就是监控器管理用的。

interface InitTask {fun init(application: Application)
}

定义了一个接口,用来初始化内存监控任务。参数是需要传递Application,但是这里没有看到有使用到。

OOMMonitor.INSTANCE.startLoop

        OOMMonitor.INSTANCE.startLoop(true, false,5_000L);

上面配置好咯参数和回调,这里就是开始循环。下面来看看里面做了什么。


object OOMMonitor : LoopMonitor<OOMMonitorConfig>(), LifecycleEventObserver {
@Volatileprivate var mIsLoopStarted = false
...override fun startLoop(clearQueue: Boolean, postAtFront: Boolean, delayMillis: Long) {throwIfNotInitialized { return }if (!isMainProcess()) {return}MonitorLog.i(TAG, "startLoop()")if (mIsLoopStarted) {return}mIsLoopStarted = truesuper.startLoop(clearQueue, postAtFront, delayMillis)getLoopHandler().postDelayed({ async { processOldHprofFile() } }, delayMillis)}...}

判断下,假如非主线程,立刻返回。这里可以看出来,调用的地方必须是主线程,不然它就不会执行。
来看下mIsLoopStarted,它被Volatile修饰。Volatile的作用是可以把对应的变量刷新到Cpu缓存中,保证了多线程环境变量的可见性。假如有其他线程修改了这个变量,那么其他线程可以立刻知道。
而这里判断假如loop已经开始,那么也return掉。这些属于健壮性代码。

super.startLoop

看下super.startLoop:

  open fun startLoop(clearQueue: Boolean = true,postAtFront: Boolean = false,delayMillis: Long = 0L) {if (clearQueue) getLoopHandler().removeCallbacks(mLoopRunnable)if (postAtFront) {getLoopHandler().postAtFrontOfQueue(mLoopRunnable)} else {getLoopHandler().postDelayed(mLoopRunnable, delayMillis)}mIsLoopStopped = false}

这里可看到围绕着mLoopRunnable来做功夫。首先看看是否需要清理之前的mLoopRunnable,接着根据参数,决定把runable post到消息队列的哪种情况中,这个稍后研究。这里先看看哪里传入的Handler。

通过跳转,找到了这里:

package com.kwai.koom.base.loopimport android.os.Handler
import android.os.HandlerThread
import android.os.Process.THREAD_PRIORITY_BACKGROUNDinternal object LoopThread : HandlerThread("LoopThread", THREAD_PRIORITY_BACKGROUND) {init {start()}internal val LOOP_HANDLER = Handler(LoopThread.looper)
}

这里是一个HandlerThread,至于HandlerThread。并且LoopThread它在初始化就执行start方法来启动线程。

接着看mLoopRunnable

protected open fun getLoopInterval(): Long {return DEFAULT_LOOP_INTERVAL}companion object {private const val DEFAULT_LOOP_INTERVAL = 1000L}private val mLoopRunnable = object : Runnable {override fun run() {if (call() == LoopState.Terminate) {return}if (mIsLoopStopped) {return}getLoopHandler().removeCallbacks(this)getLoopHandler().postDelayed(this, getLoopInterval())}}

这里就是拿到handler,执行postDelayed,间隔设置为1秒。

call() == LoopState.Terminate

这行代码是关键,假如LoopState.Terminate,是结束状态的话,那就执行call方法。
在这里插入图片描述
看下OOMMonitor的实现:

  override fun call(): LoopState {if (!sdkVersionMatch()) {return LoopState.Terminate}if (mHasDumped) {return LoopState.Terminate}return trackOOM()}

假如dump完成,就返回terminate状态。继续看trackOOM方法:

 private fun trackOOM(): LoopState {SystemInfo.refresh()mTrackReasons.clear()for (oomTracker in mOOMTrackers) {if (oomTracker.track()) {mTrackReasons.add(oomTracker.reason())}}if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {if (isExceedAnalysisPeriod() || isExceedAnalysisTimes()) {MonitorLog.e(TAG, "Triggered, but exceed analysis times or period!")} else {async {MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")dumpAndAnalysis()}}return LoopState.Terminate}return LoopState.Continue}

看下refresh方法:

var procStatus = ProcStatus()var lastProcStatus = ProcStatus()var memInfo = MemInfo()var lastMemInfo = MemInfo()var javaHeap = JavaHeap()var lastJavaHeap = JavaHeap()fun refresh() {lastJavaHeap = javaHeaplastMemInfo = memInfolastProcStatus = procStatusjavaHeap = JavaHeap()procStatus = ProcStatus()memInfo = MemInfo()javaHeap.max = Runtime.getRuntime().maxMemory()javaHeap.total = Runtime.getRuntime().totalMemory()javaHeap.free = Runtime.getRuntime().freeMemory()javaHeap.used = javaHeap.total - javaHeap.freejavaHeap.rate = 1.0f * javaHeap.used / javaHeap.maxFile("/proc/self/status").forEachLineQuietly { line ->if (procStatus.vssInKb != 0 && procStatus.rssInKb != 0&& procStatus.thread != 0) return@forEachLineQuietlywhen {line.startsWith("VmSize") -> {procStatus.vssInKb = VSS_REGEX.matchValue(line)}line.startsWith("VmRSS") -> {procStatus.rssInKb = RSS_REGEX.matchValue(line)}line.startsWith("Threads") -> {procStatus.thread = THREADS_REGEX.matchValue(line)}}}File("/proc/meminfo").forEachLineQuietly { line ->when {line.startsWith("MemTotal") -> {memInfo.totalInKb = MEM_TOTAL_REGEX.matchValue(line)}line.startsWith("MemFree") -> {memInfo.freeInKb = MEM_FREE_REGEX.matchValue(line)}line.startsWith("MemAvailable") -> {memInfo.availableInKb = MEM_AVA_REGEX.matchValue(line)}line.startsWith("CmaTotal") -> {memInfo.cmaTotal = MEM_CMA_REGEX.matchValue(line)}line.startsWith("ION_heap") -> {memInfo.IONHeap = MEM_ION_REGEX.matchValue(line)}}}memInfo.rate = 1.0f * memInfo.availableInKb / memInfo.totalInKbMonitorLog.i(TAG, "----OOM Monitor Memory----")MonitorLog.i(TAG,"[java] max:${javaHeap.max} used ratio:${(javaHeap.rate * 100).toInt()}%")MonitorLog.i(TAG,"[proc] VmSize:${procStatus.vssInKb}kB VmRss:${procStatus.rssInKb}kB " + "Threads:${procStatus.thread}")MonitorLog.i(TAG,"[meminfo] MemTotal:${memInfo.totalInKb}kB MemFree:${memInfo.freeInKb}kB " + "MemAvailable:${memInfo.availableInKb}kB")MonitorLog.i(TAG,"avaliable ratio:${(memInfo.rate * 100).toInt()}% CmaTotal:${memInfo.cmaTotal}kB ION_heap:${memInfo.IONHeap}kB")}

SystemInfo类里面有很多Java堆,内存信息,进程状态相关的类。这里面可以看出,这个类就是用来把一些监控到的数据刷新和写入文件里面的。当然,还有log输出。

再看mOOMTrackers,分别是各个跟踪器

  private val mOOMTrackers = mutableListOf(HeapOOMTracker(), ThreadOOMTracker(), FdOOMTracker(),PhysicalMemoryOOMTracker(), FastHugeMemoryOOMTracker())

他们抽象父类是:

abstract class OOMTracker : Monitor<OOMMonitorConfig>() {/*** @return true 表示追踪到oom、 false 表示没有追踪到oom*/abstract fun track(): Boolean/*** 重置track状态*/abstract fun reset()/*** @return 追踪到的oom的标识*/abstract fun reason(): String
}

至于具体怎么track,由于篇幅和内容方向问题,这篇文章先不进一步分析。留到后面的文章继续。

回到trackOOM方法:

   mTrackReasons.clear()for (oomTracker in mOOMTrackers) {if (oomTracker.track()) {mTrackReasons.add(oomTracker.reason())}}if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {if (isExceedAnalysisPeriod() || isExceedAnalysisTimes()) {MonitorLog.e(TAG, "Triggered, but exceed analysis times or period!")} else {async {MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")dumpAndAnalysis()}}

假如track到了原因,它就添加mTrackReasons。
假如分析超过时间和次数,就打印error。其它正常情况就打印mTrackReasons,执行dumpAndAnalysis,然后返回LoopState.Terminate状态。

下面重点看看dumpAndAnalysis方法:

dumpAndAnalysis

 private fun dumpAndAnalysis() {MonitorLog.i(TAG, "dumpAndAnalysis");runCatching {if (!OOMFileManager.isSpaceEnough()) {MonitorLog.e(TAG, "available space not enough", true)return@runCatching}if (mHasDumped) {return}mHasDumped = trueval date = Date()val jsonFile = OOMFileManager.createJsonAnalysisFile(date)val hprofFile = OOMFileManager.createHprofAnalysisFile(date).apply {createNewFile()setWritable(true)setReadable(true)}MonitorLog.i(TAG, "hprof analysis dir:$hprofAnalysisDir")ForkJvmHeapDumper.getInstance().run {dump(hprofFile.absolutePath)}MonitorLog.i(TAG, "end hprof dump", true)Thread.sleep(1000) // make sure file synced to disk.MonitorLog.i(TAG, "start hprof analysis")startAnalysisService(hprofFile, jsonFile, mTrackReasons.joinToString())}.onFailure {it.printStackTrace()MonitorLog.i(TAG, "onJvmThreshold Exception " + it.message, true)}}

这里面正式把track到的数据写入到文件中,包括json文件和hprof文件。重点看dump方法:

dump

@Overridepublic synchronized boolean dump(String path) {MonitorLog.i(TAG, "dump " + path);if (!sdkVersionMatch()) {throw new UnsupportedOperationException("dump failed caused by sdk version not supported!");}init();if (!mLoadSuccess) {MonitorLog.e(TAG, "dump failed caused by so not loaded!");return false;}boolean dumpRes = false;try {MonitorLog.i(TAG, "before suspend and fork.");int pid = suspendAndFork();if (pid == 0) {// Child processDebug.dumpHprofData(path);exitProcess();} else if (pid > 0) {// Parent processdumpRes = resumeAndWait(pid);MonitorLog.i(TAG, "dump " + dumpRes + ", notify from pid " + pid);}} catch (IOException e) {MonitorLog.e(TAG, "dump failed caused by " + e);e.printStackTrace();}return dumpRes;}

init方法:

  private void init () {if (mLoadSuccess) {return;}if (loadSoQuietly("koom-fast-dump")) {mLoadSuccess = true;nativeInit();}}

这里加载一个so库,可以看到还有这些native方法:

/*** Init before do dump.*/private native void nativeInit();/*** Suspend the whole ART, and then fork a process for dumping hprof.** @return return value of fork*/private native int suspendAndFork();/*** Resume the whole ART, and then wait child process to notify.** @param pid pid of child process.*/private native boolean resumeAndWait(int pid);/*** Exit current process.*/private native void exitProcess();

接着执行suspendAndFork,也是native方法。拿到进程pid之后,fork当前进程。然后dump hprof文件。
在这里插入图片描述
至于为什么需要fork一个进程出来dump,可以通过上面截图看出来原因,dump hprof 数据的时候会触发GC,而GC会出发STW,这无疑会造成APP卡顿。这也是LeakCanary不能做成线上内存监控的主要原因,而KOOM解决了这个问题。

子进程dump工作做完之后,接着exitProcess退出。

假如pid > 0,resumeAndWait,就恢复整个ART虚拟机,然后等待子线程唤醒。

这里逻辑我说的有点不清晰,由于看不到so的代码,无法确认。有知道的大佬可以指点一下,感激。

startAnalysisService

前面fork子进程后,执行了 Thread.sleep(1000) // make sure file synced to disk.
接着看是分析堆转信息工作:

private fun startAnalysisService(hprofFile: File,jsonFile: File,reason: String) {if (hprofFile.length() == 0L) {hprofFile.delete()MonitorLog.i(TAG, "hprof file size 0", true)return}if (!getApplication().isForeground) {MonitorLog.e(TAG, "try startAnalysisService, but not foreground")mForegroundPendingRunnables.add(Runnable {startAnalysisService(hprofFile,jsonFile,reason)})return}OOMPreferenceManager.increaseAnalysisTimes()val extraData = AnalysisExtraData().apply {this.reason = reasonthis.currentPage = getApplication().currentActivity?.localClassName.orEmpty()this.usageSeconds = "${(SystemClock.elapsedRealtime() - mMonitorInitTime) / 1000}"}HeapAnalysisService.startAnalysisService(getApplication(),hprofFile.canonicalPath,jsonFile.canonicalPath,extraData,object : AnalysisReceiver.ResultCallBack {override fun onError() {MonitorLog.e(TAG, "heap analysis error, do file delete", true)hprofFile.delete()jsonFile.delete()}override fun onSuccess() {MonitorLog.i(TAG, "heap analysis success, do upload", true)val content = jsonFile.readText()MonitorLogger.addExceptionEvent(content, Logger.ExceptionType.OOM_STACKS)monitorConfig.reportUploader?.upload(jsonFile, content)monitorConfig.hprofUploader?.upload(hprofFile, OOMHprofUploader.HprofType.ORIGIN)}})}

这里就是进行针对一些dump数据进行解析、整理等工作,假如需要上传到服务器,这里也预留了接口供开发者使用,非常贴心。

到这里KOOM框架的Java层核心代码逻辑基本过完了。

回到startLoop方法

回到startLoop方法中super.startLoop 方法,下一行代码是:

    getLoopHandler().postDelayed({ async { processOldHprofFile() } }, delayMillis)

前面分析知道,getLoopHandler拿到的是HandlerThread,这里延时post一个runable消息给它。这里使用协程来执行。

重点需要关注的是processOldHprofFile。


object OOMMonitor : LoopMonitor<OOMMonitorConfig>(), LifecycleEventObserver {private const val TAG = "OOMMonitor"...private fun processOldHprofFile() {MonitorLog.i(TAG, "processHprofFile")if (mHasProcessOldHprof) {return}mHasProcessOldHprof = true;reAnalysisHprof()manualDumpHprof()}...private fun reAnalysisHprof() {for (file in hprofAnalysisDir.listFiles().orEmpty()) {if (!file.exists()) continueif (!file.name.startsWith(MonitorBuildConfig.VERSION_NAME)) {MonitorLog.i(TAG, "delete other version files ${file.name}")file.delete()continue}if (file.canonicalPath.endsWith(".hprof")) {val jsonFile = File(file.canonicalPath.replace(".hprof", ".json"))if (!jsonFile.exists()) {MonitorLog.i(TAG, "create json file and then start service")jsonFile.createNewFile()startAnalysisService(file, jsonFile, "reanalysis")} else {MonitorLog.i(TAG,if (jsonFile.length() == 0L) "last analysis isn't succeed, delete file"else "delete old files", true)jsonFile.delete()file.delete()}}}}private fun manualDumpHprof() {for (hprofFile in manualDumpDir.listFiles().orEmpty()) {MonitorLog.i(TAG, "manualDumpHprof upload:${hprofFile.absolutePath}")monitorConfig.hprofUploader?.upload(hprofFile, OOMHprofUploader.HprofType.STRIPPED)}}}

里面就是操作dump出来的文件,判断当前的版本,假如是旧的,删掉重写等逻辑。

总结

截止到这里,我们开始监控的这两行代码分析完毕:

  /** Init OOMMonitor*/OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication());OOMMonitor.INSTANCE.startLoop(true, false,5_000L);

很简单的两行代码,里面包含了如此之多的业务逻辑和精彩的设计。

很多时候,我们使用越是简单的开源框架,越是能证明作者的厉害之处。他们把繁杂的逻辑内聚到了框架里面,让使用者能用简单一两行代码实现复杂的逻辑业务。

KOOM作为一个线上内存监控框架,有很多优秀的设计。这篇文章也只是在外层分析了一些表面的技术逻辑,至于更深入的内容,后续会继续更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/733686.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用阿里云服务器搭建网站简单吗?超简单教程

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网aliyunfuwuqi.com以搭建WordPress网站博客为例&#xff0c;来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流…

Pytorch学习 day08(最大池化层、非线性激活层、正则化层、循环层、Transformer层、线性层、Dropout层)

最大池化层 最大池化&#xff0c;也叫上采样&#xff0c;是池化核在输入图像上不断移动&#xff0c;并取对应区域中的最大值&#xff0c;目的是&#xff1a;在保留输入特征的同时&#xff0c;减小输入数据量&#xff0c;加快训练。参数设置如下&#xff1a; kernel_size&#…

Linux网络基础2之协议

(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨&#xff01;你好这里是ky233的主页&#xff1a;这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 目录 1.协议 1.序列化与反序列换 2.协议定制 二…

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记10 - STM32的SDIO学习2

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记10 - STM32的SDIO学习2 一、问题回顾二、本次的任务三、 需要注意的问题3.1 Card Identification Mode时的时钟频率3.2 CMD0指令的疑似问题3.3 发送带参数的ACMD41时要注意时间时序和时效3.4 CPSM的指令发送问题3.5 调试过程中的SD卡的…

linuxOPS基础_linux系统注意事项

Linux严格区分大小写 Linux 和Windows不同&#xff0c;Linux严格区分大小写的&#xff0c;包括文件名和目录名、命令、命令选项、配置文件设置选项等。 例如&#xff0c;Win7 系统桌面上有文件夹叫做Test&#xff0c;当我们在桌面上再新建一个名为 test 的文件夹时&#xff0c…

R统计学2 - 数据分析入门问题21-40

往期R统计学文章&#xff1a; R统计学1 - 基础操作入门问题1-20 21. 如何对矩阵按行 (列) 作计算&#xff1f; 使用函数 apply() vec 1:20 # 转换为矩阵 mat matrix (vec , ncol4) # [,1] [,2] [,3] [,4] # [1,] 1 6 11 16 # [2,] 2 7 12 17 # [3,] …

嵌入式Linux串口和 poll() 函数的使用

一、poll() 函数的介绍 poll() 函数用于监控多个文件描述符的变化的函数。它可以用来检查一个或多个文件描述符的状态是否改变&#xff0c;比如是否可读、可写或有错误发生。它常用于处理 I/O 多路复用&#xff0c;这在需要同时处理多个网络连接或文件操作时非常有用。 头文件…

CentOS 7.6安装部署Seafile服务器

今天飞飞和你们分享CentOS 7.6上安装基于MySQL/MariaDB的Seafile服务器的方法&#xff0c;包括下载和安装7.0.5版本、配置数据库、启动服务器等步骤。安装成功后&#xff0c;需要通过nginx反向代理才能访问seafile服务。 通过预编译好的安装包来安装并运行基于 MySQL/MariaDB …

高吞吐SFTP连接池设计方案

背景 在现代的数据驱动环境中&#xff0c;安全文件传输协议&#xff08;SFTP&#xff09;扮演着至关重要的角色&#xff0c;它提供了一种安全、可靠的文件传输方式。我们目前项目是一个大型数据集成平台&#xff0c;跟上下游有很多文件对接是通过SFTP协议&#xff0c;当需要处…

果蔬作物疾病防治系统|基于Springboot的果蔬作物疾病防治系统设计与实现(源码+数据库+文档)

果蔬作物疾病防治系统目录 目录 基于Springboot的果蔬作物疾病防治系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、果蔬百科列表 2、公告信息管理 3、公告类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推…

【蓝桥·算法双周赛】第七场分级赛——小白入门赛

2.霓虹【算法赛】 - 蓝桥云课 (lanqiao.cn) st数组用来存第i个位置&#xff0c;这个字母有没有编号j #include<bits/stdc.h> const int N1e610; using lllong long; std::map<std::string,std::string> mp;std::string a,aa; int st[N][10];// int stt[N][10];//对…

Qt 拖动事件

文章目录 1 自定义控件 TextEdit2 实现打开文件功能3 实现鼠标滚轮放大字体 QEvent::DragEnter 当拖动文件进入到窗口/控件中时&#xff0c;触发该事件&#xff0c;它对应的子类是QDragEnterEvent QEvent::DragLeave 当拖动文件离开窗口/控件时&#xff0c;触发该事件&#xff…

WordPress高端后台美化WP Adminify Pro优化版

后台UI美化WP Adminify Pro修改自定义插件&#xff0c;适合建站公司和个人使用&#xff0c;非常高大上&#xff0c;下载地址&#xff1a;WP Adminify Pro优化版 修复记录&#xff1a; 1、修复已知BUG 2、修复手机版兼容问题 3、修复打开速度&#xff0c;原版打开速度太慢 4…

自动裁剪人脸:简化你的数字人素材准备

在做数字人时,需要对采集的数据进行预处理,然后才能进行模型训练, 预处理常用的操作有:去背景 音频重采样 视频裁剪 音频特征提取等等,今天我们来分享一个自动化脚本: 对原图/视频进行人脸检测并根据目标尺寸以人脸为中心进行裁剪. 目录 1. 效果 2. 对图片进行裁剪 3.对视频…

DeepLearning in Pytorch|共享单车预测NN详解(思路+代码剖析)

目录 概要 一、代码概览 二、详解 基本逻辑 1.数据准备 2.设计神经网络 初版 改进版 测试 总结 概要 原文链接&#xff1a;DeepLearning in Pytorch|我的第一个NN-共享单车预测 我的第一个深度学习神经网络模型---利用Pytorch设计人工神经网络对某地区租赁单车的使用…

umi4 项目使用 keepalive 缓存页面(umi-plugin-keep-alive、react-activation)

umi4使用keepalive 配置文件config\config.ts export default defineConfig({plugins: [umi-plugin-keep-alive], });安装add umi-plugin-keep-alive yarn add umi-plugin-keep-alive页面 A import { KeepAlive, history, useAliveController } from umijs/max; const Page…

CSAPP Malloc lab

CSAPP Malloc Lab 目标 实现一个简单的动态存储分配器。 评分标准 空间利用率应当减少internal 和 external fragmentation. memory utilization memory utilization payload / heap size fragmentation internal fragmentation external fragmentation throughput T 越接…

【深度学习笔记】6_9 深度循环神经网络deep-rnn

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.9 深度循环神经网络 本章到目前为止介绍的循环神经网络只有一个单向的隐藏层&#xff0c;在深度学习应用里&#xff0c;我们通常会用…

嵌入式Qt 制作一个登录对话框

一.登录对话框需求分析 二.代码实现 main.c&#xff1a; #include <QtGui/QApplication> #include "widget.h"int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); }Widget.h&#xff1a; #ifndef _WIDGET_H_…

为什么选择 Flink 做实时处理

优质博文&#xff1a;IT-BLOG-CN 为什么选择 Flink 【1】流数据更真实地反映了我们的生活方式&#xff08;实时聊天&#xff09;&#xff1b; 【2】传统的数据架构是基于有限数据集的&#xff08;Spark 是基于微批次数据处理&#xff09;&#xff1b; 【3】我们的目标&#xf…