【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 {
@Volatile
  private 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 = true

    super.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.loop

import android.os.Handler
import android.os.HandlerThread
import android.os.Process.THREAD_PRIORITY_BACKGROUND

internal 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 = javaHeap
    lastMemInfo = memInfo
    lastProcStatus = procStatus

    javaHeap = 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.free
    javaHeap.rate = 1.0f * javaHeap.used / javaHeap.max

    File("/proc/self/status").forEachLineQuietly { line ->
      if (procStatus.vssInKb != 0 && procStatus.rssInKb != 0
          && procStatus.thread != 0) return@forEachLineQuietly

      when {
        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.totalInKb

    MonitorLog.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 = true

      val 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


  @Override
  public 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 process
        Debug.dumpHprofData(path);
        exitProcess();
      } else if (pid > 0) {
        // Parent process
        dumpRes = 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 = reason
      this.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()) continue

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kubesphere前端项目分析

1 KubeSphere console功能导图 模块&#xff1a; 命令行工具 (kubectl) 日志&#xff08;Logging&#xff09; 平台设置&#xff08;Platform Settings&#xff09; 服务组件&#xff08;Service Components&#xff09; 监控和警报&#xff08;Monitoring & Alerting&…

手写分布式配置中心(六)整合springboot(自动刷新)

对于springboot配置自动刷新&#xff0c;原理也很简单&#xff0c;就是在启动过程中用一个BeanPostProcessor去收集需要自动刷新的字段&#xff0c;然后在springboot启动后开启轮询任务即可。 不过需要对之前的代码再次做修改&#xff0c;因为springboot的配置注入value("…

pytest教程-15-多个fixture以及重命名

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了fixture的yield关键字&#xff0c;本小节我们讲解一下使用多个fixture的方法。 使用多个fixture 如果用例需要用到多个fixture的返回数据&#xff0c;fixture也可以return一个元组、list或字…

[嵌入式系统-37]:龙芯1B 开发学习套件 -7-MIPS指令集

目录 一、MIPS指令分类 二、常用指令详解 三、常用MIPS指令集及格式&#xff1a; 四、CPU内部的32个寄存器&#xff1a; 一、MIPS指令分类 MIPS&#xff08;Microprocessor without Interlocked Pipeline Stages&#xff09;指令集是一种广泛用于教学和嵌入式系统的指令集…

在线部署ubuntu20.04服务器,安装jdk、mysql、redis、nginx、minio、开机自启微服务jar包

一、服务器 1、查看服务器版本 查看服务器版本为20.04 lsb_release -a2、服务器信息 服务器初始账号密码 sxd / 123456 首先,更改自身密码都输入123456 sudo passwd 创建最高权限root账号&#xff0c;密码为 123456 su root 3、更新服务器源 1、更新源列表 sudo apt-g…

【golang】Windows与Linux交叉编译保姆级教程

【golang】Windows与Linux交叉编译 大家好 我是寸铁&#x1f44a; 总结了一篇【golang】Windows与Linux交叉编译的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 问题背景 今天寸铁想将Windows中的程序部到Linux下跑&#xff0c;我们知道在从Windows与Linux下要进行交叉编译…

11、Linux-安装和配置Redis

目录 第一步&#xff0c;传输文件和解压 第二步&#xff0c;安装gcc编译器 第三步&#xff0c;编译Redis 第四步&#xff0c;安装Redis服务 第五步&#xff0c;配置Redis ①开启后台启动 ②关闭保护模式&#xff08;关闭之后才可以远程连接Redis&#xff09; ③设置远程…

Java基础 - 8 - 算法、正则表达式、异常

一. 算法 什么是算法&#xff1f; 解决某个实际问题的过程和方法 学习算法的技巧&#xff1f; 先搞清楚算法的流程&#xff0c;再直接去推敲如何写算法 1.1 排序算法 1.1.1 冒泡排序 每次从数组中找出最大值放在数组的后面去 public class demo {public static void main(S…

21 easy 1. 两数之和

//给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 // // 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 // // 你可以…

java Spring boot简述jetcache 并叙述后续文章安排

我们之前 讲了 Spring boot 整合 cache 使用 simple(默认) redis Ehcache memcached的几种方式 但是 始终有人觉得不够完善 提出了一些问题 例如 觉得 当前spring boot 对缓存过期的控制过于松散 不严谨 比较明显的体现就是 memcached过期时间在逻辑代码中控制 Ehcache的过期时…

Tomcat的安装

下载Tomcat&#xff08;这里以Tomcat8.5为例&#xff09; 直接进入官网进行下载&#xff0c;Tomcat官网 选择需要下载的版本&#xff0c;点击下载这里一定要注意&#xff1a;下载路径一定要记住&#xff0c;并且路径中尽量不要有中文&#xff01;&#xff01;&#xff01;&…

Prompt Engineering、Finetune、RAG:OpenAI LLM 应用最佳实践

一、背景 本文介绍了 2023 年 11 月 OpenAI DevDay 中的一个演讲&#xff0c;演讲者为 John Allard 和 Colin Jarvis。演讲中&#xff0c;作者对 LLM 应用落地过程中遇到的问题和相关改进方案进行了总结。虽然其中用到的都是已知的技术&#xff0c;但是进行了很好的总结和串联…

神经网络的矢量化,训练与激活函数

我们现在再回到我们的神经元部分&#xff0c;来看我们如何用python进行正向传递。 单层的正向传递&#xff1a; 我们回到我们的线性回归的函数。我们每个神经元通过上述的方法&#xff0c;就可以得到我们的激发值&#xff0c;从而可以继续进行下一层。 我们用这个方法就可以得…

排序算法的对比

类别排序方法时间复杂度空间复杂度稳定性平均情况特殊情况 插入 排序 插入排序基本有序最优稳定希尔排序不稳定 选择 排序 选择排序不稳定堆排序不稳定 交换 排序 冒泡排序稳定快速排序基本有序最差不稳定归并排序稳定基数排序稳定

0环PEB断链实现

截止到昨天那里我们的思路就清晰了&#xff0c;通过EPROCESS找到我们要隐藏的进程的ActiveProcessLinks&#xff0c;将双向链表的值修改&#xff0c;就可以将我们想要隐藏的这个进程的ActiveProcessLinks从双向链表中抹去的效果&#xff0c;这里的话如果在windbg里面直接使用ed…

用Python实现一个简单的——人脸相似度对比

近几年来&#xff0c;兴起了一股人工智能热潮&#xff0c;让人们见到了AI的能力和强大&#xff0c;比如图像识别&#xff0c;语音识别&#xff0c;机器翻译&#xff0c;无人驾驶等等。总体来说&#xff0c;AI的门槛还是比较高&#xff0c;不仅要学会使用框架实现&#xff0c;更…

Day33|贪心算法part3

k次取反后最大的数组元素和 思路&#xff1a;贪心&#xff0c;局部最优&#xff0c;让绝对值大的负数变正数&#xff0c;当前数值达到最大&#xff0c;整体最优&#xff1b;整个数组和达到最大。如果把序列中所有负数都转换为正数了&#xff0c;k还没耗尽&#xff0c;就是k还大…

AWS的CISO:GenAI只是一个工具,不是万能钥匙

根据CrowdStrike的年度全球威胁报告,尽管研究人员预计人工智能将放大对防御者和攻击者的影响,但威胁参与者在其行动中使用人工智能的程度有限。该公司上个月在报告中表示:“在整个2023年,很少观察到GenAI支持恶意计算机网络运营的开发和/或执行。” 对于GenAI在网络安全中的…

Python:在 Ubuntu 上安装 pip的方法

目录 1、检测是否已安装pip 2、更新软件源 3、安装 4、检测是否安装成功 pip和pip3都是Python包管理工具&#xff0c;用于安装和管理Python包。 在Ubuntu上&#xff0c;pip和pip3是分别针对Python2和Python3版本的pip工具。 pip3作用&#xff1a;自动下载安装Python的库文…

2024年HW技术总结

我们都知道&#xff0c; 护网行动 是国家应对网络安全问题所做的重要布局之一。至今已经是8个年头了&#xff0c;很多公司在这时候人手不够&#xff0c;因此不得不招募一些网安人员来参加护网。 红队 扮演攻击的角色&#xff0c;蓝队 扮演防守、溯源的角色&#xff0c;紫队当然…