【性能优化】使用Perfetto定位应用启动性能的瓶颈

Android应用启动优化相关的文章已经有很多人都写过了,但是主要都是聚焦在,为了启动性能都做了哪些改动上,少见有文章会说应该如何分析、识别应用的启动性能。

本篇文章将会结合我个人对Perfetto的实际使用经历,讲解车载应用的启动时间是如何测量得到的,测量出启动时间后,我们又该如何找出其中的性能瓶颈。

在分析应用的启动性能之前,我们先简单了解一些Android中有关应用启动时间的基础性常识。

应用启动时间

初始显示时间(TTID)

初始显示时间 (TTID,The Time to Initial Display) ,它是从系统接收到启动意图到应用程序显示第一帧界面的时间,也就是用户看到应用程序界面的时间。

测量TTID

当应用程序完成上面提到的所有工作时,可以在logcat中看到以下的日志输出。

/system_process I/ActivityTaskManager: Displayed xxxx/.MainActivity: +401ms

在所有资源完全加载并显示之前,Logcat输出中的Displayed时间指标,省去了布局文件中未引用的资源或应用作为对象初始化一部分创建资源的时间。

有的时候logcat输出中的日志中会包含一个附加字段total。如下所示:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

在这种情况下,第一个时间测量值仅针对第一个绘制的 activity。total 时间测量值是从应用进程启动时开始计算,并且可以包含首次启动但未在屏幕上显示任何内容的另一个 activity。total 时间测量值仅在单个activity的时间和总启动时间之间存在差异时才会显示。

一些博客中介绍的使用am start -S -W命令来测得的时间,实际上就是初始显示时间。大多数情况下,初始显示时间并不能代表应用真正的启动时间,例如,应用启动时需要从网络上同步最新的数据,当所有的数据加载完毕后的耗时才是真正的启动耗时,这个就是接下来要介绍的TTFD - 完全显示时间

完全显示的时间(TTFD)

完全显示时间 (TTFD,The Time to Full Display) 。它是从系统接收到启动意图到应用程序加载完成所有资源和视图层次结构的时间,也就是用户可以真正使用应用程序的时间。

测量TTFD

方法一:调用reportFullyDrawn()

reportFullyDrawn()方法可以在应用程序加载完成所有资源和视图层次结构后调用,让系统知道应用程序已经完全显示,从而计算出完全显示时间。如果不调用这个方法,系统只能计算出TTID而无法得知TTFD。

system_process I/ActivityTaskManager: Fully drawn xxxx/.MainActivity: +1s54ms

方法二:拆帧

拆帧法是目前计算车载应用启动耗时时最普遍的做法,拆帧法有许多不同的录制、拆帧方式。

常见的有,使用支持60fps的摄像机(支持60fps摄像的手机也可以)拍摄应用的启动视频,再使用Potplay视频播放器查看从桌面点击应用画面完全显示出来的帧数差值,然后除以60就可以得到应用的启动耗时。

以上方法适合测试人员使用,这里介绍另一种更适合开发人员操作的方式:FFmpeg拆帧。

FFmpeg 的下载地址:http://ffmpeg.org/download.html?aemtn=tg-on

首先使用adb连接Android设备,使用录屏指令录制应用启动时的视频。

adb shell screenrecord /sdcard/launch.mp4

使用FFmpeg查看视频的帧数

ffmpeg -i launch.mp4 

图片

如果视频帧数不足60fps,继续使用FFmpeg将视频补帧到60fps.

ffmpeg -i launch.mp4 -filter:v fps=60 output.mp4

将补帧后的视频,每一帧拆成一张图片,然后计算出从桌面点击应用画面完全显示出来的帧数差值即可。

ffmpeg -i output.mp4 output_%04d.jpg

或将补帧后的视频转换成gif动图,使用图片浏览器数帧(MAC OS自带的图片浏览器就可以)。

ffmpeg -i output.mp4 -vf fps=60,scale=320:-1:flags=lanczos -loop 0 output.gif

关于应用的启动时间和测量方式就介绍到这里,更多内容可以参考Android的官方文档「应用启动时间 | 应用质量 | 安卓」,写得非常详细。

值得一提的是,当前主流车载应用的平均启动耗时(以8155平台为例)如下:

  • • 冷启动TTFD

第三方大型互联网应用需要控制在2.6s以下,车载系统应用需控制在1.6s以下,

  • • 温启动TTFD

普遍需控制在0.8s以下。

以上是我个人的经验,不同的主机厂商肯定会存在高低不同的性能要求。

Perfetto 介绍

Perfetto 快速上手

Perfetto是Android 10 引入的系统级跟踪工具,支持Android,Linux和Chrome,用于取代Systrace 。相比于ProfilerAGI,它不再局限于应用内,而是可以提供整个系统的运行状态,当我们需要查看应用有没有影响到系统的稳定性和流畅性时,或者反过来用于分析系统对应用运行的影响时,就可以使用Perfetto来进行系统级跟踪和分析。

有关Perfetto的基础内容,可以查看我之前翻译的Android官方视频:【译】现代Android开发技能 - Perfetto入门

Perfetto的使用方式有很多,个人建议使用record_android_trace脚本。它是Perfetto提供的一个辅助脚本,可以帮助我们使用adb从Android设备上收集性能数据。这个脚本有以下作用:

  • • 自动检测设备上是否有perfetto二进制文件,如果没有,就尝试从GitHub下载并推送到设备上。

  • • 自动设置跟踪的配置参数,例如跟踪时间、缓冲区大小、输出文件路径等。

  • • 自动执行perfetto命令,并在跟踪完成后将输出文件拉取到电脑上。

  • • 自动在浏览器中打开输出文件,让你可以查看和分析跟踪结果。

record_android_trace的用法如下:

./record_android_trace [options] [category1] [category2] ...

其中,options是一些可选的参数,例如:

  • • -o OUT_FILE:指定输出文件的路径,如果不指定,默认为 perfetto_trace.pb。

  • • -t TIME:指定跟踪的时间,如果不指定,默认为 10 秒。

  • • -b SIZE:指定跟踪的缓冲区大小,如果不指定,默认为 32 MB。

category是一些要跟踪的atrace或ftrace类别,可以使用--list查看设备支持的Trace类别,输出结果可能如下:

 
  1. link@link-PC:~/Desktop$ ./record_android_trace --list

  2.          gfx - Graphics

  3.        input - Input

  4.         view - View System

  5.      webview - WebView

  6.           wm - Window Manager

  7.           am - Activity Manager

  8.           sm - Sync Manager

  9.        audio - Audio

  10.        video - Video

  11.       camera - Camera

  12.          hal - Hardware Modules

  13.          res - Resource Loading

  14.       dalvik - Dalvik VM

  15.           rs - RenderScript

  16.       bionic - Bionic C Library

  17.        power - Power Management

  18.           pm - Package Manager

  19.           ss - System Server

  20.     database - Database

  21.      network - Network

  22.          adb - ADB

  23.     vibrator - Vibrator

  24.         aidl - AIDL calls

  25.        nnapi - NNAPI

  26.          rro - Runtime Resource Overlay

  27.          pdx - PDX services

  28.        sched - CPU Scheduling

  29.          irq - IRQ Events

  30.          i2c - I2C Events

  31.         freq - CPU Frequency

  32.         idle - CPU Idle

  33.         disk - Disk I/O

  34.         sync - Synchronization

  35.        workq - Kernel Workqueues

  36.   memreclaim - Kernel Memory Reclaim

  37.   regulators - Voltage and Current Regulators

  38.   binder_driver - Binder Kernel driver

  39.   binder_lock - Binder global lock trace

  40.    pagecache - Page cache

  41.       memory - Memory

  42.      thermal - Thermal event

  43.          gfx - Graphics (HAL)

  44.          ion - ION allocation (HAL)

例如,如果想要跟踪 sched、gfx和view,输出文件为 trace.perfetto-trace,跟踪时间为 5 秒,缓冲区大小为 16 MB,可以执行以下命令:

./record_android_trace -o trace.perfetto-trace -t 5s -b 16mb sched gfx view
Perfetto 分析启动性能

使用Perfetto分析应用的启动性能非常简单,首先使用record_android_trace抓取应用的启动数据,执行如下指令:

./record_android_trace -o trace.perfetto-trace -t 15s -b 200mb gfx input view webview wm am sm audio video camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl nnapi rro pdx sched irq i2c freq idle disk sync workq memreclaim regulators binder_driver binder_lock pagecache memory thermal gfx ion

15s后record_android_trace会自动帮我们打开浏览器。在Android App Startups一栏展示的就是应用的启动耗时。如下所示,需要注意的是Android App Startups显示的时间是应用的TTID - 初始显示时间

图片

在Perfetto的左侧选择Metrics,然后选择android_startup,点击Run,Perfetto会自动帮我们分析中应用启动时的各项数据,如下所示。

图片

 
  1. android_startup {

  2.   startup {

  3.     startup_id: 1

  4.     startup_type: "warm"

  5.     package_name: "com.xxx.xxx.weather"

  6.     process_name: "com.xxx.xxx.weather"

  7.     process {

  8.       name: "com.xxx.xxx.weather"

  9.       uid: 1000

  10.       pid: 3376

  11.     }

  12.     zygote_new_process: false

  13.     activity_hosting_process_count: 1

  14.     event_timestamps {

  15.       intent_received: 100680138137

  16.       first_frame: 102167532928

  17.     }

  18.     to_first_frame {

  19.       dur_ns: 1487394791

  20.       dur_ms: 1487.394791

  21.       main_thread_by_task_state {

  22.         running_dur_ns: 1316606193

  23.         runnable_dur_ns: 34121303

  24.         uninterruptible_sleep_dur_ns: 20429636

  25.         interruptible_sleep_dur_ns: 84415940

  26.         uninterruptible_io_sleep_dur_ns: 12221457

  27.         uninterruptible_non_io_sleep_dur_ns: 8208179

  28.       }

  29.       time_activity_manager {

  30.         dur_ns: 16070209

  31.         dur_ms: 16.070209

  32.       }

  33.       time_activity_start {

  34.         dur_ns: 97578437

  35.         dur_ms: 97.578437

  36.       }

  37.       time_activity_resume {

  38.         dur_ns: 833413073

  39.         dur_ms: 833.413073

  40.       }

  41.       time_choreographer {

  42.         dur_ns: 481555469

  43.         dur_ms: 481.555469

  44.       }

  45.       time_inflate {

  46.         dur_ns: 1241538748

  47.         dur_ms: 1241.538748

  48.       }

  49.       time_get_resources {

  50.         dur_ns: 6173178

  51.         dur_ms: 6.173178

  52.       }

  53.       time_verify_class {

  54.         dur_ns: 1675365

  55.         dur_ms: 1.675365

  56.       }

  57.       time_gc_total {

  58.         dur_ns: 82049531

  59.         dur_ms: 82.049531

  60.       }

  61.       time_dlopen_thread_main {

  62.         dur_ns: 15522344

  63.         dur_ms: 15.522344

  64.       }

  65.       time_lock_contention_thread_main {

  66.         dur_ns: 4711976

  67.         dur_ms: 4.711976

  68.       }

  69.       time_jit_thread_pool_on_cpu {

  70.         dur_ns: 375033124

  71.         dur_ms: 375.033124

  72.       }

  73.       time_gc_on_cpu {

  74.         dur_ns: 81314427

  75.         dur_ms: 81.314427

  76.       }

  77.       jit_compiled_methods: 218

  78.       other_processes_spawned_count: 6

  79.     }

  80.     verify_class {

  81.       name: "com.xxx.xxx.weather.service.VoiceActionManager"

  82.       dur_ns: 1675365

  83.     }

  84.     dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libffavc.so"

  85.     dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libpag.so"

  86.     dlopen_file: "/vendor/lib64/hw/android.hardware.graphics.mapper@4.0-impl-qti-display.so"

  87.     dlopen_file: "libadreno_utils.so"

  88.     dlopen_file: "/vendor/lib64/hw/android.hardware.graphics.mapper@3.0-impl-qti-display.so"

  89.     dlopen_file: "/vendor/lib64/hw/gralloc.msmnile.so"

  90.     dlopen_file: "libadreno_app_profiles.so"

  91.     dlopen_file: "libEGL_adreno.so"

  92.     system_state {

  93.       dex2oat_running: false

  94.       installd_running: false

  95.       broadcast_dispatched_count: 0

  96.       broadcast_received_count: 0

  97.       most_active_non_launch_processes: "media.codec"

  98.       most_active_non_launch_processes: "app_process"

  99.       most_active_non_launch_processes: "media.hwcodec"

  100.       most_active_non_launch_processes: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"

  101.       most_active_non_launch_processes: "/system/bin/audioserver"

  102.       installd_dur_ns: 0

  103.       dex2oat_dur_ns: 0

  104.     }

  105. slow_start_reason: "GC Activity"

  106. slow_start_reason: "Main Thread - Time spent in Running state"

  107. slow_start_reason: "Time spent in view inflation"

  108.   }

  109. }

android_startup是一个用于记录和分析Android应用启动性能的数据结构,它包含了应用启动过程中的各种信息,例如启动类型、启动时间、启动原因、启动依赖、系统状态等。

android_startup的内容是一个protobuf格式的文本,它表示了一个名为com.xxx.xxx.weather的天气应用的启动数据。其中,最重要的是最后一段的slow_start_reason,它向我们展示了应用可能导致启动缓慢的原因,在第三节我们会重点分析。

其他字段的含义如下:

startup_id: 是一个唯一标识符,表示这是第一次启动;

startup_type: 是一个枚举类型,表示这是一个warm(温)启动,即应用进程已经存在,但没有活动在前台;

package_nameprocess_name表示应用的包名和进程名;

process: 表示应用进程的信息,包括名称、用户标识符(uid)和进程标识符(pid);

zygote_new_process: 表示是否通过zygote创建了新进程,这里为false;

activity_hosting_process_count: 表示有多少个活动托管在这个进程中,这里为1;

event_timestamps: 表示各种事件发生的时间戳,例如intent_received表示收到启动意图的时间,first_frame表示显示第一帧画面的时间;

to_first_frame: 表示从收到启动意图到显示第一帧画面所花费的时间和细节,包括总时间、主线程各种状态的时间、各种操作的时间、各种资源的使用情况等;

verify_class: 表示验证类加载的信息,包括类名和时间;

dlopen_file: 表示打开共享库文件的信息,包括文件名;

system_state: 表示系统状态的信息,包括是否有dex2oat或installd在运行、是否有广播发送或接收、哪些非启动进程最活跃等;

Perfetto实践

启动时触发GC

现象slow_start_reason中出现"GC Activity",表示在启动阶段GC活动拖慢了应用程序的启动。

分析:点击【show timeline】返回到Perfetto时间轴界面。在启动时间轴中,可以看到有一个线程名为HeapTaskDaemon,他就是应用程序的GC线程,在启动阶段活跃了约100ms左右,导致activityResume的时间轴也被拉长100ms。为防止是偶发现现象,进行了多次测量,发现该应用启动时必定会触发GC活动。如图所示:

图片

原因:根据时间轴向前分析,发现该应用在启动阶段会加载一个特殊字体,该字体约13MB,经过与应用的开发沟通,确认该字体已经移动到系统层,应用层不必加载该字体。移除字体后,应用启动时不再100%触发GC。

主线程耗时操作

现象slow_start_reason出现 "Main Thread - Time spent in Running state",表示启动阶段,主线程中执行较多的耗时操作。

原因:这种情况在应用开发时很常见,一些跨进程的获取数据的操作,应用开发人员会很自然的将其放在主线程Activity的OnCreate或onStart方法下执行,这些IPC方法虽然不至于触发ANR,但是会拖慢应用的启动,应该放置到线程池或协程中执行。

OpenDexFilesFromOat耗时

现象slow_start_reason出现"Main Thread - Time spent in OpenDexFilesFromOat*",表示启动阶段,花费了较多时间在读取dex文件上。

图片

原因:这种情况在车载Android系统中较为常见。这可能是因为系统为了加快启动速度,修改了系统中dex2oat的流程,导致此现象,耗时不多的话可以忽略。

这里只是说是“可能”,是因为如今的车载OS为了快速启动,对原生Android的修改非常多,我们需要结合自身的实际情况再做详细分析。

连续多帧绘制超时

现象: 某个应用在Perfetto中的启动时间并不长,大约在1.3s左右,但是使用拆帧法后,发现该应用启动后会有些许卡顿,导致实际启动时间拉长到2.1s。表现在Perfetto上如下所示,在第一帧绘制完毕后,后续2、4、5帧的绘制时间都超过了150ms。

图片

分析:Perfetto给出的帧绘制时间轴显示大部分时间花费在View的Layout上,这说明在首帧绘制完毕后,又触发了多次页面重绘。

原因:通过代码结合应用的日志,发现该应用在启动时会使用空数据刷新一次页面,然后会从IPC接口中再获取一次数据更新页面,而且由于代码缺陷,数据刷新会连续执行4次,导致了该情况的发现。修改缺陷代码后,首帧之后就不会发生连续绘制超时的情况了。

参考资料

https://developer.android.com/topic/performance/vitals/launch-time#time-initial

转自:【性能优化】使用Perfetto定位应用启动性能的瓶颈

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

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

相关文章

前端post传入拿到数据,后端报null,并且能够添加或者编辑成功

检查conterller层注解接到实体类的注解是不是没加( RequestBody ) 后端: 前端: 那么就看注解,因为contrller层有个接值注解( RequestBody )

MySQL基础练习题44-只出现一次的最大数字

目录 题目 情况一 准备数据 分析数据 情况二 准备数据 实现一 题目 单一数字 是在 MyNumbers 表中只出现一次的数字。 找出最大的 单一数字 。如果不存在 单一数字 ,则返回 null 。 情况一 准备数据 ## 创建库 create database db; use db;## 创建表 Cre…

代码随想录算法训练营Day42||Leetcode300.最长递增子序列 、 674. 最长连续递增序列 、 718. 最长重复子数组

一、最长递增子序列 简单&#xff0c;只不过返回值不是dp数组最后一个元素了&#xff0c;自己做出来AC了 class Solution { public:int lengthOfLIS(vector<int>& nums) {vector<int>dp(nums.size()1,1);for(int i1;i<nums.size();i){for(int ji-1;j>0…

自动化运维---ansible

ansible是一种由Python开发的自动化运维工具&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 特点: 部署简单 默认使用ssh进行管理&#xff0c;基于py…

万能钥匙:解锁 C++ 模板的无限可能

1.泛型编程 1.1:交换两个数(C语言) 1.2:交换两个数(C) 1.3:泛型编程 2:函数模板 2.1:函数模板的概念 2.2:函数模板的格式 ​编辑 2.3:函数模板的原理 2.4:模板的实例化 2.4.1:隐式实例化 2.4.2:显式实例化:在函数名后的<>中指定模板参数的实际类型. 2.4.2.1…

Docker 部署 XXL-JOB

Docker 部署 XXL-JOB 目录 引言环境准备创建 MySQL 用户并授予权限使用 Docker 部署 XXL-JOB配置 XXL-JOB验证部署总结 1. 引言 XXL-JOB 是一个开源的分布式任务调度平台&#xff0c;旨在简化定时任务的管理和调度操作。其强大的功能和灵活性&#xff0c;使其在互联网公司和…

WebSocket 快速入门

WebSocket是什么 WebSocket 是基于 TCP 的一种新的应用层网络协议。它实现了浏览器与服务器全双工通信&#xff0c;即允许服务器主动发送信息给客户端。因此&#xff0c;在 WebSocket 中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性…

谷歌开源Gemma-2 百亿参数大模型,性能超越Llama-3模型,免费使用

Gemma 模型 Gemma模型是谷歌发布的一个开源模型&#xff0c;任何人都可以免费下载预训练模型&#xff0c;进行使用。而谷歌最近也发布了Gemma 2 模型&#xff0c;模型参数超过了 200 亿大官&#xff0c;果真大模型最后都是拼参数的时候吗。 Gemma 2 模型发布 Gemma 2 模型可以…

使用 Python 解密加密的 PDF 文件

使用 Python 进行 PDF 文件加密-CSDN博客文章浏览阅读89次&#xff0c;点赞2次&#xff0c;收藏2次。定义一个名为的函数&#xff0c;该函数接受三个参数&#xff1a;输入的 PDF 文件路径input_pdf、输出的加密 PDF 文件路径output_pdf和密码password。https://blog.csdn.net/q…

Ubuntu中设置环境变量 PATH 的命令,不生效的问题“PATH=~/bin:$PATH”

1. 知识点 PATH~/bin:$PATH PATH&#xff1a;这是一个环境变量&#xff0c;用于指定操作系统在哪些目录中查找可执行文件。 ~&#xff1a;这是一个特殊的符号&#xff0c;代表当前用户的主目录。 /bin&#xff1a;这通常是存放标准实用程序&#xff08;如 ls, cp 等&#xff…

为什么神经网络常常是linear+relu的堆叠

特征提取&#xff1a;每一层的线性变换可以看作是在提取输入数据的不同特征。通过堆叠多个这样的层&#xff0c;网络能够学习从原始数据中提取越来越复杂的特征表示非线性关系&#xff1a;单个神经元的线性变换是线性的&#xff0c;但通过引入非线性激活函数&#xff08;例如Re…

【vue讲解:vue3介绍、setup、ref、reactive、监听属性、生命周期、toRef、setup写法】

1 vue3介绍 # Vue3的变化-vue3完全兼容vue2---》但是vue3不建议用vue2的写法-拥抱TypeScript-之前咱们用的JavaScript---》ts完全兼容js- 组合式API和配置项APIvue2 是配置项apivue3 组合式api# vue4必须要用2 vue3项目创建和启动 # 创建vue3项目-vue-cli 官方不太建议用了…

C语言典型例题40

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 题目 例题3.8 运输公司对用户计算运费。路程&#xff08;以s表示&#xff0c;单位为千米&#xff09;&#xff0c;吨/千米运费越低。标准如下&#xff1a; s<250 没…

深度学习中的模型架构详解

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;模型架构的不断发展极大地推动了技术的进步。从早期的循环神经网络&#xff08;RNN&#xff09;到长短期记忆网络&#xff08;LSTM&#xff09;、再到卷积神经网络&#xff08;TextCNN&#xff09;和Transformer&…

完美解决html2canvas + jsPDF导出pdf分页内容截断问题

代码地址&#xff1a;https://github.com/HFQ12333/export-pdf.git html2canvas jspdf方案是前端实现页面打印的一种常用方案&#xff0c;但是在实践过程中&#xff0c;遇到的最大问题就是分页截断的问题&#xff1a;当页面元素超过一页A4纸的时候&#xff0c;连续的页面就会…

python处理时间,按照周分割时间段

在实际的开发中&#xff0c;我们经常要设计一些工具类&#xff0c;对于时间来说&#xff0c;有时候需要将其处理成时间段。 例如&#xff0c;对于2024年08月01日到2024年08月16日的时间段&#xff0c;我们如何将其处理成时间段[2024-08-01, 2024-08-03], [2024-08-04, 2024-08-…

OSL 冠名赞助Web3峰会 “FORESIGHT2024”圆满收官

OSL 望为香港数字资产市场发展建设添砖加瓦 &#xff08;香港&#xff0c;2024 年 8 月 13 日&#xff09;- 8 月 11 日至 12 日&#xff0c; 由 香港唯一专注数字资产的上市公司 OSL 集团&#xff08;863.HK&#xff09;冠名赞助&#xff0c;Foresight News、 Foresight Ventu…

基于免疫算法的最优物流仓储点选址方案MATLAB仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于免疫算法的最优物流仓储点选址方案MATLAB仿真。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 &#xff08;完整程序运行后无水印&#xff09; 3…

pytorch-AutoEncoders

目录 1. 监督学习&无监督学习1.1 监督学习1.2 无监督学习1.3 为什么需要无监督学习 2. AutoEncoders3. Auto Encoders loss function4. PCA VS Auto Encoders5. Auto Encoders的变种5.1 Denoising Auto Encoders5.2 Dropout AutoEncoders5.3 Adversarial AutoEncoders5.4 V…

html 关于table合并外边框以及自动滚动问题汇总

合并外边框 .tab_main{ width: 100%; height:100%; border: 1px solid #ccc; text-align: center; border-spacing: 0; border-collapse: collapse;//合并外边框 } 固定高度显示上下滑动 <div styleoverflow:scroll;height:100%> <di…