首页 > 其他分享 >架构师必知的11种API性能优化方法

架构师必知的11种API性能优化方法

时间:2024-06-22 17:53:27浏览次数:33  
标签:11 加锁 必知 调用 用户 接口 查询 索引 API

前言

接口性能优化是后端开发人员经常碰到的一道面试题,因为它是一个跟开发语言无关的公共问题。

这个问题既可以很简单,也可以相当复杂。

有时候,只需要添加一个索引就能解决。
有时候,代码需要进行重构。
有时候,必须增加缓存。
有时候,需要引入一些中间件,例如消息队列(MQ)。
有时候,需进行分库分表。
有时候,需要拆分服务。
等等。

导致接口性能问题的原因多种多样,不同项目的不同接口,其原因可能各不相同。

这里,小北给大家做一下系统化、体系化的梳理,使大家在面试过程中能够清晰、有条理的回答出面试官的提问,让面试官 “眼前一亮、口水直流”,然后实现”offer直提”。

插播一条:如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题。

一、索引优化

接口性能优化时,大家第一个想到的通常是:优化索引。

确实,优化索引的成本是最小的。

你可以通过查看线上日志或监控报告,发现某个接口使用的某条SQL语句耗时较长。

此时,你可能会有以下疑问:

这条SQL语句是否已经加了索引?
加的索引是否生效了?
MySQL是否选择了错误的索引?

1.1 没加索引

在SQL语句中,忘记为WHERE条件的关键字段或ORDER BY后的排序字段加索引是项目中常见的问题。

在项目初期,由于表中的数据量较小,加不加索引对SQL查询性能影响不大。

然而,随着业务的发展,表中的数据量不断增加,这时就必须加索引了。

可以通过以下命令查看/添加索引:

show index from `table_name`
CREATE INDEX index_name ON table_name (column_name);

这种方式能够显著提高查询性能,尤其是在数据量庞大的情况下。

1.2 索引没生效

通过上述命令我们已经确认索引是存在的,但它是否生效呢?

此时,你可能会有这样的疑问。

那么,如何查看索引是否生效呢?

答案是:可以使用 EXPLAIN 命令,查看 MySQL 的执行计划,它会显示索引的使用情况。

例如:

EXPLAIN SELECT * FROM `order` WHERE code='002';

结果:

这个命令将显示查询的执行计划,包括使用了哪些索引。

如果索引生效,你会在输出结果中看到相关的信息。

通过这几列可以判断索引使用情况,执行计划包含列的含义如下图所示:

说实话,SQL语句没有使用索引,除去没有建索引的情况外,最大的可能性是索引失效了。

以下是索引失效的常见原因:

了解这些原因,可以帮助你在查询优化时避免索引失效的问题,确保数据库查询性能保持最佳。

1.3 选错索引

此外,你是否遇到过这样一种情况:明明是同一条SQL语句,只是入参不同。

有时候使用的是索引A,有时候却使用索引B?

没错,有时候MySQL会选错索引。

必要时可以使用 FORCE INDEX 来强制查询SQL使用某个索引。

例如:

SELECT * FROM `order` FORCE INDEX (index_name) WHERE code='002';

至于为什么MySQL会选错索引,原因可能有以下几点:

了解这些原因,可以帮助你更好地理解和控制MySQL的索引选择行为,确保查询性能的稳定性。

插播一条:如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题。

二、SQL优化

如果优化了索引之后效果不明显,接下来可以尝试优化一下SQL语句,因为相对于修改Java代码来说,改造SQL语句的成本要小得多。

以下是SQL优化的15个小技巧:

三、远程调用

多时候,我们需要在一个接口中调用其他服务的接口。

例如,有这样的业务场景:

在用户信息查询接口中需要返回以下信息:用户名称、性别、等级、头像、积分和成长值。

其中,用户名称、性别、等级和头像存储在用户服务中,积分存储在积分服务中,成长值存储在成长值服务中。为了将这些数据统一返回,我们需要提供一个额外的对外接口服务。

因此,用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后将数据汇总并统一返回。

调用过程如下图所示:

调用远程接口总耗时 530ms = 200ms + 150ms + 180ms

显然这种串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。

那么如何优化远程接口性能呢?

3.1 串行改并行

上面说到,既然串行调用多个远程接口性能很差,为什么不改成并行呢?

如下图所示:

调用远程接口的总耗时为200ms,这等于耗时最长的那次远程接口调用时间。

在Java 8之前,可以通过实现Callable接口来获取线程的返回结果。

在Java 8之后,可以通过CompletableFuture类来实现这一功能。

以下是一个使用CompletableFuture的示例:

public class RemoteServiceExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 调用用户服务接口
        CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
            // 模拟远程调用
            simulateDelay(200);
            return "User Info";
        });
        // 调用积分服务接口
        CompletableFuture<String> pointsFuture = CompletableFuture.supplyAsync(() -> {
            // 模拟远程调用
            simulateDelay(150);
            return "Points Info";
        });
        // 调用成长值服务接口
        CompletableFuture<String> growthFuture = CompletableFuture.supplyAsync(() -> {
            // 模拟远程调用
            simulateDelay(100);
            return "Growth Info";
        });
        // 汇总结果
        CompletableFuture<Void> allOf = CompletableFuture.allOf(userFuture, pointsFuture, growthFuture);
        // 等待所有异步操作完成
        allOf.join();
        // 获取结果
        String userInfo = userFuture.get();
        String pointsInfo = pointsFuture.get();
        String growthInfo = growthFuture.get();
    }
}

3.2 数据异构

为了提升接口性能,尤其在高并发场景下,可以考虑数据冗余,将用户信息、积分和成长值的数据统一存储在一个地方,比如Redis。

这样,通过用户ID可以直接从Redis中查询所需的数据,从而避免远程接口调用

但需要注意的是,如果使用了数据异构方案,就可能会出现数据一致性问题。

用户信息、积分和成长值有更新的话,大部分情况下,会先更新到数据库,然后同步到redis。

但这种跨库的操作,可能会导致两边数据不一致的情况产生。

那如何解决数据一致性问题呢?

由于篇幅有限,本文就不展开详细说这块了,感兴趣的同学可以看我的另一篇文章《数据一致性》

四、重复调用

在我们的日常工作代码中,重复调用非常常见,但如果没有控制好,会严重影响接口的性能。

让我们一起来看看这个问题。

4.1 循环查数据库
有时候,我们需要从指定的用户集合中查询出哪些用户已经存在于数据库中。

一种实现方式如下:

public List<User> findExistingUsers(List<String> userIds) {
    List<User> existingUsers = new ArrayList<>();
    for (String userId : userIds) {
        User user = userRepository.findById(userId);
        if (user != null) {
            existingUsers.add(user);
        }
    }
    return existingUsers;
}

上述代码会对每个用户ID执行一次数据库查询,这在用户集合较大时会导致性能问题。

那么,我们如何优化呢?

我们可以通过批量查询来优化性能,减少数据库的查询次数。

public List<User> findExistingUsers(List<String> userIds) {
    // 批量查询数据库
    List<User> users = userRepository.findByIds(userIds);
    return users;
}

这里有个需要注意的地方是:id集合的大小要做限制,最好一次不要请求太多的数据。要根据实际情况而定,建议控制每次请求的记录条数在500以内。

五、异步处理

在进行接口性能优化时,有时候需要重新梳理业务逻辑,检查是否存在设计不合理的地方。

假设有一个用户请求接口,需要执行以下操作:

  1. 业务操作
  2. 发送站内通知
  3. 记录操作日志
    为了实现方便,通常会将这些逻辑放在接口中同步执行,但这会对接口性能造成一定影响。

这个接口表面上看起来没有问题,但如果你仔细梳理一下业务逻辑,会发现只有业务操作才是核心逻辑,其他的功能都是非核心逻辑。

在这里有个原则就是:

核心逻辑可以同步执行,同步写库。非核心逻辑,可以异步执行,异步写库。

上面这个例子中,发站内通知和用户操作日志功能,对实时性要求不高,即使晚点写库,用户无非是晚点收到站内通知,或者运营晚点看到用户操作日志,对业务影响不大,所以完全可以异步处理。

异步处理方案

异步处理通常有两种主要方式:多线程和消息队列(MQ)

5.1 线程池异步优化

使用线程池改造之后,接口逻辑如下

5.2 MQ异步

使用线程池有个小问题就是:如果服务器重启了,或者是需要被执行的功能出现异常了,无法重试,会丢数据。

为了避免使用线程池处理异步任务时出现数据丢失的问题,可以考虑使用更加健壮和可靠的异步处理方案,如消息队列(MQ)。消息队列不仅可以异步处理任务,还能够保证消息的持久化和可靠性,支持重试机制。

使用mq改造之后,接口逻辑如下

插播一条:如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题。

六、避免大事务

很多小伙伴在使用Spring框架开发项目时,为了方便,喜欢使用@Transactional注解提供事务功能。

没错,使用@Transactional注解这种声明式事务的方式提供事务功能,确实能少写很多代码,提升开发效率。

但也容易造成大事务,引发性能的问题。

那么我们该如何优化大事务呢?

为了避免大事务引发的问题,可以考虑以下优化建议:

  1. 少用@Transactional注解
  2. 将查询(select)方法放到事务外
  3. 事务中避免远程调用
  4. 事务中避免一次性处理太多数据
  5. 有些功能可以非事务执行
  6. 有些功能可以异步处理

七、锁粒度

在一些业务场景中,为了避免多个线程并发修改同一共享数据而引发数据异常,通常我们会使用加锁的方式来解决这个问题。

然而,如果锁的设计不当,导致锁的粒度过粗,也会对接口性能产生显著的负面影响。

7.1 synchronized

在Java中,我们可以使用synchronized关键字来为代码加锁。

通常有两种写法:在方法上加锁和在代码块上加锁。

1. 方法上加锁

public synchronized void doSave(String fileUrl) {
    mkdir();
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

在方法上加锁的目的是为了防止并发情况下创建相同的目录,避免第二次创建失败而影响业务功能。

但这种直接在方法上加锁的方式,锁的粒度较粗。

因为doSave方法中的文件上传和消息发送并不需要加锁,只有创建目录的方法需要加锁。

我们知道,文件上传操作非常耗时,如果将整个方法加锁,那么需要等到整个方法执行完之后才能释放锁。

显然,这会导致该方法的性能下降,得不偿失。

2. 代码块上加锁
我们可以将加锁改在代码块上,从而缩小锁的粒度,
如下:

public void doSave(String path, String fileUrl) {
    synchronized(this) {
        if (!exists(path)) {
            mkdir(path);
        }
    }
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

这样改造后,锁的粒度变小了,只有并发创建目录时才加锁。

创建目录是一个非常快的操作,即使加锁对接口性能的影响也不大。

最重要的是,其他的文件上传和消息发送功能仍然可以并发执行。

多节点环境中的问题
在单机版服务中,这种做法没有问题。但在生产环境中,为了保证服务的稳定性,同一个服务通常会部署在多个节点上。如果某个节点挂掉,其他节点的服务仍然可用。

多节点部署避免了某个节点挂掉导致服务不可用的情况,同时也能分摊整个系统的流量,避免系统压力过大。

但这种部署方式也带来了新的问题:synchronized只能保证一个节点加锁有效。

如果有多个节点,如何加锁呢?

7.2 Redis分布式锁

在分布式系统中,由于Redis分布式锁的实现相对简单且高效,因此它在许多实际业务场景中被广泛采用。

使用Redis分布式锁的伪代码如下:

public boolean doSave(String path, String fileUrl) {
    try {
        String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
        if ("OK".equals(result)) {
            if (!exists(path)) {
                mkdir(path);
                uploadFile(fileUrl);
                sendMessage(fileUrl);
            }
            return true;
        }
    } finally {
        unlock(lockKey, requestId);
    }
    return false;
}

与之前使用synchronized关键字加锁时一样,这里的锁的范围也太大了,换句话说,锁的粒度太粗。这会导致整个方法的执行效率很低。

实际上,只有在创建目录时才需要加分布式锁,其余代码不需要加锁。

于是,我们需要优化代码:

public void doSave(String path, String fileUrl) {
    if (tryLock()) {
        try {
            if (!exists(path)) {
                mkdir(path);
            }
        } finally {
            unlock(lockKey, requestId);
        }
    }
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

private boolean tryLock() {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    return "OK".equals(result);
}

private void unlock(String lockKey, String requestId) {
    // 解锁逻辑
}

上面的代码将加锁的范围缩小了,只有在创建目录时才加锁。这样的简单优化后,接口性能可以得到显著提升。

7.3 数据库锁

MySQL数据库中的三种锁

  1. 表锁

    • 优点:加锁快,不会出现死锁。
    • 缺点:锁定粒度大,锁冲突的概率高,并发度最低。
  2. 行锁

    • 优点:锁定粒度最小,锁冲突的概率低,并发度最高。
    • 缺点:加锁慢,会出现死锁。
  3. 间隙锁

    • 优点:锁定粒度介于表锁和行锁之间。
    • 缺点:开销和加锁时间介于表锁和行锁之间,并发度一般,也会出现死锁。

锁与并发度

并发度越高,接口性能越好。因此,数据库锁的优化方向是:

  1. 优先使用行锁
  2. 其次使用间隙锁
  3. 最后使用表锁

插播一条:如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题。

八、分页处理

有时候,我需要调用某个接口来批量查询数据,例如,通过用户ID批量查询用户信息,然后为这些用户赠送积分。

但是,如果一次性查询的用户数量太多,例如一次查询2000个用户的数据,传入2000个用户的ID进行远程调用时,用户查询接口经常会出现超时的情况。

调用代码如下:

List<User> users = remoteCallUser(ids);

众所周知,调用接口从数据库获取数据需要经过网络传输。如果数据量过大,无论是数据获取速度还是网络传输速度都会受到带宽限制,从而导致耗时较长。

那么,这种情况下该如何优化呢?

答案是:分页处理。

将一次性获取所有数据的请求,改为分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。

其实,处理这个问题可以分为两种场景:同步调用和异步调用。

8.1 同步调用

如果在job中需要获取2000个用户的信息,它要求只要能正确获取到数据即可,对获取数据的总耗时要求不高。

但对每一次远程接口调用的耗时有要求,不能大于500ms,否则会有邮件预警。

这时,我们可以同步分页调用批量查询用户信息接口。

具体示例代码如下:

List<List<Long>> allIds = Lists.partition(ids, 200);

for (List<Long> batchIds : allIds) {
    List<User> users = remoteCallUser(batchIds);
}

代码中我使用了Google Guava工具中的Lists.partition方法,用它来做分页简直太好用了,不然要写一大堆分页的代码。
8.2 异步调用
如果是在某个接口中需要获取2000个用户的信息,需要考虑的因素更多。

除了远程调用接口的耗时,还需要考虑该接口本身的总耗时,也不能超过500ms。

这时,使用上面的同步分页请求远程接口的方法肯定是行不通的。

那么,只能使用异步调用了。

代码如下:

List<List<Long>> allIds = Lists.partition(ids, 200);

final List<User> result = Lists.newArrayList();
allIds.stream().forEach(batchIds -> {
    CompletableFuture.supplyAsync(() -> {
        result.addAll(remoteCallUser(batchIds));
        return Boolean.TRUE;
    }, executor);
});

使用CompletableFuture类,通过多个线程异步调用远程接口,最后汇总结果统一返回。

九、加缓存

通常情况下,我们最常用的缓存是:Redis和Memcached。

但对于Java应用来说,绝大多数情况下使用的是Redis,所以接下来我们以Redis为例。

在关系型数据库(例如:MySQL)中,菜单通常有上下级关系。某个四级分类是某个三级分类的子分类,三级分类是某个二级分类的子分类,而二级分类又是某个一级分类的子分类。

这种存储结构决定了,想一次性查出整个分类树并非易事。这需要使用程序递归查询,而如果分类很多,这个递归操作会非常耗时。

因此,如果每次都直接从数据库中查询分类树的数据,会是一个非常耗时的操作。

这时我们可以使用缓存。在大多数情况下,接口直接从缓存中获取数据。操作Redis可以使用成熟的框架,比如:Jedis和Redisson等。
使用Jedis的伪代码如下:

String json = jedis.get(key);
if (StringUtils.isNotEmpty(json)) {
    CategoryTree categoryTree = JsonUtil.toObject(json);
    return categoryTree;
}
return queryCategoryTreeFromDb();

注意引入缓存之后,我们的系统复杂度就上升了,这时候就会存在数据不一致的问题

如何解决数据不一致的问题,感兴趣的小伙伴可以看我的另一篇文章,《》

十、分库分表

有时候,接口性能受限的并不是其他方面,而是数据库。

当系统发展到一定阶段,用户并发量增加,会有大量的数据库请求,这不仅需要占用大量的数据库连接,还会带来磁盘IO的性能瓶颈问题。

此外,随着用户数量的不断增加,产生的数据量也越来越大,一张表可能无法存储所有数据。由于数据量太大,即使SQL语句使用了索引,查询数据时也会非常耗时。

那么,这种情况下该怎么办呢?

答案是:需要进行分库分表。

如下图所示:

图中将用户库拆分成了三个库,每个库都包含了四张用户表。

如果有用户请求过来,先根据用户ID路由到其中一个用户库,然后再定位到某张表。

路由的算法有很多:

  1. 根据ID取模

    • 例如:ID=7,有4张表,则7%4=3,模为3,路由到用户表3。
  2. 给ID指定一个区间范围

    • 例如:ID的值是0-10万,则数据存在用户表0;ID的值是10-20万,则数据存在用户表1。
  3. 一致性Hash算法
    分库分表主要有两个方向:垂直和水平。

1. 垂直分库分表
垂直分库分表(即业务方向)更简单,将不同的业务数据存储在不同的库或表中。

例如,将用户数据和订单数据存储在不同的库中。

2. 水平分库分表
水平分库分表(即数据方向)上,分库和分表的作用有区别,不能混为一谈。

分库

  • 目的:解决数据库连接资源不足问题和磁盘IO的性能瓶颈问题。

分表

  • 目的:解决单表数据量太大,SQL语句查询数据时,即使走了索引也非常耗时的问题。此外,还可以解决消耗CPU资源的问题。

分库分表

  • 目的:综合解决数据库连接资源不足、磁盘IO性能瓶颈、数据检索耗时和CPU资源消耗等问题。

业务场景中的应用

  1. 只分库

    • 用户并发量大,但需要保存的数据量很少。
  2. 只分表

    • 用户并发量不大,但需要保存的数据量很多。
  3. 分库分表

    • 用户并发量大,并且需要保存的数据量也很多。

十一、监控功能

优化接口性能问题,除了上面提到的这些常用方法之外,还需要配合使用一些辅助功能,因为它们真的可以帮我们提升查找问题的效率。

11.1 开启慢查询日志

通常情况下,为了定位SQL的性能瓶颈,我们需要开启MySQL的慢查询日志。把超过指定时间的SQL语句单独记录下来,方便以后分析和定位问题。

开启慢查询日志需要重点关注三个参数:

  • slow_query_log:慢查询开关
  • slow_query_log_file:慢查询日志存放的路径
  • long_query_time:超过多少秒才会记录日志

通过MySQL的SET命令可以设置:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/usr/local/mysql/data/slow.log';
SET GLOBAL long_query_time = 2;

设置完之后,如果某条SQL的执行时间超过了2秒,会被自动记录到slow.log文件中。

当然,也可以直接修改配置文件my.cnf:

[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 2

但这种方式需要重启MySQL服务。

很多公司每天早上都会发一封慢查询日志的邮件,开发人员根据这些信息优化SQL。

11.2 加监控

为了在出现SQL问题时能够及时发现,我们需要对系统做监控。

目前业界使用比较多的开源监控系统是:Prometheus。

它提供了监控和预警的功能。

架构图如下:

我们可以用它监控如下信息:

  • 接口响应时间
  • 调用第三方服务耗时
  • 慢查询sql耗时
  • cpu使用情况
  • 内存使用情况
  • 磁盘使用情况
  • 数据库使用情况
  • 等等。。。

它的界面大概长这样子:

可以看到MySQL的当前QPS、活跃线程数、连接数、缓存池的大小等信息。

如果发现连接池占用的数据量太多,肯定会对接口性能造成影响。

这时可能是由于代码中开启了连接却忘记关闭,或者并发量太大导致的,需要进一步排查和系统优化

链路跟踪

有时候,一个接口涉及的逻辑非常复杂,例如查询数据库、查询Redis、远程调用接口、发送MQ消息以及执行业务代码等等。

这种情况下,接口的一次请求会涉及到非常长的调用链路。如果逐一排查这些问题,会耗费大量时间,此时我们已经无法用传统的方法来定位问题。

有没有办法解决这个问题呢?

答案是使用分布式链路跟踪系统:SkyWalking。

SkyWalking的架构图如下:

在SkyWalking中,可以通过traceId(全局唯一的ID)来串联一个接口请求的完整链路。你可以看到整个接口的耗时、调用的远程服务的耗时、访问数据库或者Redis的耗时等,功能非常强大。

之前没有这个功能时,为了定位线上接口性能问题,我们需要在代码中加日志,手动打印出链路中各个环节的耗时情况,然后再逐一排查。这种方法不仅费时费力,而且容易遗漏细节。

如果你用过SkyWalking来排查接口性能问题,你会不自觉地爱上它的功能。如果你想了解更多功能,可以访问SkyWalking的官网:skywalking.apache.org。

总结

认真看到这里的同学,相信已经对API接口性能优化有一个清晰的、系统的认知了,如果在面试中能够完整的说出这11种API接口性能优化的思路,相信面试官一定会对你刮目相看的。

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!

真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

标签:11,加锁,必知,调用,用户,接口,查询,索引,API
From: https://www.cnblogs.com/jiang-xiao-bei/p/18262575

相关文章

  • [本科项目实训] 行动模拟实现 与 FastAPI 对应交互-V0
    to2024/06/01行动模拟该项目拟通过大语言模型分析拆解人类描述的任务,自动的调用行动库进行执行并完成内容的汇报。因而需要针对任务对大模型进行微调(比如本项目使用的p-tuningv2),行动模拟即针对微调结果将大模型与行动库进行对接,模拟实际场景下的函数输入输出。第一步,即加......
  • 使用 Web Share API 分享内容
    WebShareAPI是一个新的标准,允许网页向操作系统的原生分享功能发起请求。这使得用户可以更方便地分享网页上的内容到其他应用或者社交媒体上。什么是WebShareAPI?WebShareAPI是一个JavaScriptAPI,允许网页开发者通过调用浏览器原生分享功能,让用户分享内容到其他应......
  • mach(zig):从webGPU api了解渲染通道&shader
    学opengl3.3教程,你将了解通用图形渲染api。学vulkan,你将用1000+行代码画一个三角形。学webGPUapi,你将获得跨平台的图形api设计规范。reddit上有人说:OpenGL只是与3D硬件连接的几个应用程序接口之一。所有这些应用程序接口的基本概念都是一样的,即使应用程序本身的方式略有不......
  • 字符串---最常用的11个魔法
    join--连接符split--以字符分割strip--去除空格或者指定字符upper--全部字符大写lower--全部字符小写find--查找字符串中某个字符的索引值len()--计算字符长度range(0,100)--列出0,1,2,3...99的数字for循环--最常用的循环索引--比如test='......
  • 314 API Versions 01(UrlSegmentApiVersionReader)
    示例1、准备两个版本的CitiesController.cs删除不在需要的TestController.csControllers文件夹下新建v1文件夹,将CitiesController.cs移动到v1中,弹出的对话框点击OK和Yes;新建v2文件夹,拷贝一份CitiesController.cs,命名空间改成v2;v2CitiesController.cs如下usingSyste......
  • 316 API Versions 03(Enabling API Versions in Swagger)
    更新Program.cs添加两个版本的SwaggerDoc//Swaggerbuilder.Services.AddEndpointsApiExplorer();//generatesdescriptionforallendpointsbuilder.Services.AddSwaggerGen(options=>{options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory,"api.x......
  • Android14之java层:增加系统API(二百二十)
    简介:CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!优质专栏:Audio工程师进阶系列【原创干货持续更新中……】......
  • day11 - 集合+学生管理系统(黑马程序员)
    目录1.ArrayList1.1ArrayList类概述1.2ArrayList类常用方法1.2.1构造方法1.2.2成员方法1.2.3示例代码1.3ArrayList存储字符串并遍历1.3.1案例需求1.3.2代码实现1.4ArrayList存储整数并遍历1.4.1案例需求1.4.2代码实现​1.5 ArrayList存储学生对象并遍......
  • ch11 特征选择与稀疏学习
    子集选择与评价缓解维度灾难的另一种重要方法是进行特征筛选,同时它也能降低学习任务的难度,只留下关键特征。对当前学习任务有用的属性称为“相关特征”,而对当前学习任务没有用的属性称为“无关特征”,包含信息能被其他特征表示的属性称为“冗余特征”。如果想要从原始特征集中选......
  • 深度学习--tensorflow中操作张量的高频率api--87
    目录1.创建张量2.shape操作3.数学运算4逻辑运算5.张量之间的操作6.数据类型的转换7.聚合(规约)操作8argmax1.创建张量tf.constant(value,dtype=None,shape=None,name='Const')tf.zeros(shape,dtype=tf.float32,name=None)tf.ones(shape,dtype=tf.float32,name......