Разберитесь с принципом реализации epoll в Linux в одной статье
Разберитесь с принципом реализации epoll в Linux в одной статье

1. Использование epoll

Давайте сначала рассмотрим использование epoll.

В следующем коде сначала используйте epoll_create для создания дескриптора файла epoll epfd, затем используйте epoll_ctl для добавления отслеживаемого сокета в epfd и, наконец, вызовите epoll_wait для ожидания данных.

Язык кода:javascript
копировать
int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...);
listen(s, ...)

int epfd = epoll_create(...);
epoll_ctl(epfd, ...); // Все, что нужно контролировать socket добавить в epfd середина.

#define MAX_EVENTS 10
struct epoll_event events[MAX_EVENTS];
while(1) {
    int n = epoll_wait(epfd, events, ...); // Возврат готов socket количество.
    for(int i = 0; i < n; ++i) {
        if (events[n].data.fd == listen_sock) {
        	// иметь дело с
		}
    }
}

Когда вы создаете сокет, операционная система создает объект сокета, которым управляет файловая система. Этот объект сокета содержит такие элементы, как буфер отправки, буфер приема и очередь ожидания. Очередь ожидания — очень важная структура, которая указывает на все процессы, которым необходимо дождаться события сокета.

2.Создание epoll

Чтобы использовать epoll, сначала необходимо вызвать функцию epoll_create() для создания дескриптора файла epoll. Прототип функции выглядит следующим образом:

Язык кода:javascript
копировать
int epoll_create(int size);

Размер параметра является устаревшим по историческим причинам и не имеет значения, начиная с Linux 2.6.8, но должен быть больше нуля.

Когда пользователь вызывает функцию epoll_create(), он входит в пространство ядра и вызывает функцию ядра do_epoll_create() для создания дескриптора epoll. Код функции do_epoll_create() выглядит следующим образом:

Язык кода:javascript
копировать
/*
 * Open an eventpoll file descriptor.
 */
static int do_epoll_create(int flags)
{
	int error, fd;
	struct eventpoll *ep = NULL;
	struct file *file;

	/* Check the EPOLL_* constant for consistency.  */
	BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);

	if (flags & ~EPOLL_CLOEXEC)
		return -EINVAL;
	/*
	 * Create the internal data structure ("struct eventpoll").
	 */
	error = ep_alloc(&ep);
	if (error < 0)
		return error;
	/*
	 * Creates all the items needed to setup an eventpoll file. That is,
	 * a file structure and a free file descriptor.
	 */
	fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
	if (fd < 0) {
		error = fd;
		goto out_free_ep;
	}
	file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
				 O_RDWR | (flags & O_CLOEXEC));
	if (IS_ERR(file)) {
		error = PTR_ERR(file);
		goto out_free_fd;
	}
	ep->file = file;
	fd_install(fd, file);
	return fd;

out_free_fd:
	put_unused_fd(fd);
out_free_ep:
	ep_free(ep);
	return error;
}

sys_epoll_create() в основном делает две вещи:

  1. вызов ep_alloc() Функциисоздавать и инициализировать eventpoll объект.
  2. вызов anon_inode_getfd() функция Пучок eventpoll сопоставление объекта с одним дескриптором файл и вернуть этот дескриптор файла。

3. структура объекта epoll

Как видно из исходного кода do_epoll_create(), объект epoll на самом деле представляет собой опрос событий, определенный следующим образом:

Язык кода:javascript
копировать
struct eventpoll {
    /* Protect the access to this structure */
    spinlock_t lock;

    /*
* This mutex is used to ensure that files are not removed
* while epoll is using them. This is held during the event
* collection loop, the file cleanup path, the epoll file exit
* code and the ctl operations.
*/
    struct mutex mtx;

    /* Wait queue used by sys_epoll_wait() */
    wait_queue_head_t wq;

    /* Wait queue used by file->poll() */
    wait_queue_head_t poll_wait;

    /* List of ready file descriptors */
    struct list_head rdllist;

    /* RB tree root used to store monitored fd structs */
    struct rb_root rbr;

    /*
* This is a single linked list that chains all the "struct epitem" that
* happened while transferring ready events to userspace w/out
* holding ->lock.
*/
    struct epitem *ovflist;

    /* wakeup_source used when ep_scan_ready_list is running */
    struct wakeup_source *ws;

    /* The user that created the eventpoll descriptor */
    struct user_struct *user;

    struct file *file;

    /* used to optimize loop detection check */
    int visited;
    struct list_head visited_list_link;
}

Есть несколько участников, на которых необходимо сосредоточить внимание:

(1) Список контролируемых сокетов (rbr).

Добавьте отслеживаемый сокет в список, сохраненный в красно-черном дереве, с помощью epoll_ctl(2). Ключ узла красно-черного дерева представляет собой дескриптор файла, а значение — это информационный эпитемент, связанный с дескриптором файла.

(2) Очередь ожидания (wq).

и socket Точно так же у него также будет очередь ожидания при вызове epoll_wait(epfd, ...) добавлю процесс в eventpoll Объект wq Ожидание в очереди.

(3) Готовый список сокетов (rdllist).

Сохраните список готовых дескрипторов файлов сокетов. Когда программа выполняет epoll_wait, если готовый сокет уже существует в списке rdlist, то epoll_wait возвращается напрямую. Если список rdlist пуст, процесс блокируется.

На следующем рисунке показана связь между объектом eventpoll и отслеживаемыми файлами:

Поскольку отслеживаемый файл передается epitem Объекты являются управляемыми, поэтому все узлы на рисунке выше основаны на epitem Он существует в форме Объекта. Почему использовать красно-черное дерево для управления отслеживаемыми файлами? Это для того, чтобы иметь возможность передавать дескриптор файлбыстро найти соответствующий epitem объект.

Язык кода:javascript
копировать
/*
 * Each file descriptor added to the eventpoll interface will
 * have an entry of this type linked to the "rbr" RB tree.
 * Avoid increasing the size of this struct, there can be many thousands
 * of these on a server and we do not want this to take another cache line.
 */
struct epitem {
	union {
		/* RB tree node links this structure to the eventpoll RB tree */
		struct rb_node rbn;
		/* Used to free the struct epitem */
		struct rcu_head rcu;
	};

	/* List header used to link this structure to the eventpoll ready list */
	struct list_head rdllink;

	/*
	 * Works together "struct eventpoll"->ovflist in keeping the
	 * single linked chain of items.
	 */
	struct epitem *next;

	/* The file descriptor information this item refers to */
	struct epoll_filefd ffd;

	/*
	 * Protected by file->f_lock, true for to-be-released epitem already
	 * removed from the "struct file" items list; together with
	 * eventpoll->refcount orchestrates "struct eventpoll" disposal
	 */
	bool dying;

	/* List containing poll wait queues */
	struct eppoll_entry *pwqlist;

	/* The "container" of this item */
	struct eventpoll *ep;

	/* List header used to link this item to the "struct file" items list */
	struct hlist_node fllink;

	/* wakeup_source used when EPOLLWAKEUP is set */
	struct wakeup_source __rcu *ws;

	/* The structure that describe the interested events and the source fd */
	struct epoll_event event;
};

epitem используется для представления информации о каждом отслеживаемом дескрипторе файла в epoll. Вот несколько важных полей:

  • rbn: узел красно-черного дерева, используемый для связи структуры эпитема с красно-черным деревом eventpoll.
  • rdllink: Готовый список, связанный с eventpoll для быстрого доступа.
  • ffd: информация о дескрипторе файла, на которую ссылается этот элемент.
  • pwqlist: очередь ожидания.
  • ep: содержит текущий элемент eventpoll объектуказатель。
  • event:Опишите интересизсобытиеиисточник fd。

4. Добавьте дескриптор файла в epoll.

Ранее мы рассказали, как создать epoll. Далее мы расскажем, как добавить отслеживаемый сокет в epoll.

Файлы, подлежащие мониторингу, можно добавить в epoll, вызвав функцию epoll_ctl(), прототип которой следующий:

Язык кода:javascript
копировать
long epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

Ниже объясняется функция каждого параметра:

  1. epfd: проходитьвызов epoll_create() дескриптор возврата функции файла。
  2. op: Операции, которые необходимо выполнить, 3 параметры.
    • EPOLL_CTL_ADD: указывает, что должна быть выполнена операция добавления.
    • EPOLL_CTL_DEL: указывает, что требуется операция удаления.
    • EPOLL_CTL_MOD: указывает, что требуется операция модификации.
  3. fd: файловый дескриптор для мониторинга.
  4. событие: сообщает ядру, какие события ему нужно прослушивать. Его определение следующее:
Язык кода:javascript
копировать
struct epoll_event {
    __uint32_t events;  /* Epoll events */
    epoll_data_t data;  /* User data variable */
};

события могут представлять собой набор следующих макросов:

  • EPOLLIN: Указывает, что соответствующий дескриптор файла может быть прочитан (включая нормальное закрытие однорангового SOCKET).
  • EPOLLOUT: указывает, что соответствующий дескриптор файла может быть записан.
  • EPOLLPRI: указывает, что соответствующий дескриптор файла имеет срочные данные для чтения.
  • EPOLLERR: Указывает, что произошла ошибка в соответствующем дескрипторе файла.
  • EPOLLHUP: Указывает, что соответствующий дескриптор файла завис.
  • EPOLLET: установите EPOLL в режим с запуском по фронту, который соответствует режиму с запуском по уровню.
  • EPOLLONESHOT: прослушивает событие только один раз. Если после прослушивания этого события вам все еще нужно продолжать мониторинг этого сокета, вам необходимо снова добавить этот сокет в очередь EPOLL.

data используется для сохранения пользовательских данных.

Функция epoll_ctl() вызывает функцию ядра do_epoll_ctl(). Реализация do_epoll_ctl() следующая:

Язык кода:javascript
копировать
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds, bool nonblock)
{
    ...
    f = fdget(epfd);
    if (!f.file)
		goto error_return;
    
    /* Get the "struct file *" for the target file */
	tf = fdget(fd);
	if (!tf.file)
		goto error_fput;
		
    ...
    
    /*
	 * At this point it is safe to assume that the "private_data" contains
	 * our own data structure.
	 */
	ep = f.file->private_data;

	error = epoll_mutex_lock(&ep->mtx, 0, nonblock);
	if (error)
		goto error_tgt_fput;
   
    ...

	/*
	 * Try to lookup the file inside our RB tree. Since we grabbed "mtx"
	 * above, we can be sure to be able to use the item looked up by
	 * ep_find() till we release the mutex.
	 */
	epi = ep_find(ep, tf.file, fd);

	error = -EINVAL;
	switch (op) {
	case EPOLL_CTL_ADD:
		if (!epi) {
			epds->events |= EPOLLERR | EPOLLHUP;
			error = ep_insert(ep, epds, tf.file, fd, full_check);
		} else
			error = -EEXIST;
		break;
	case EPOLL_CTL_DEL:
		if (epi)
			error = ep_remove(ep, epi);
		else
			error = -ENOENT;
		break;
	case EPOLL_CTL_MOD:
		if (epi) {
			if (!(epi->event.events & EPOLLEXCLUSIVE)) {
				epds->events |= EPOLLERR | EPOLLHUP;
				error = ep_modify(ep, epi, epds);
			}
		} else
			error = -ENOENT;
		break;
	}
	mutex_unlock(&ep->mtx);

error_tgt_fput:
	if (full_check) {
		clear_tfile_check_list();
		loop_check_gen++;
		mutex_unlock(&epmutex);
	}

	fdput(tf);
error_fput:
	fdput(f);
error_return:

	return error;
}

Функция sys_epoll_ctl() будет выполнять различные операции на основе значений различных переданных операций. Например, если EPOLL_CTL_ADD передается, чтобы указать, что должна быть выполнена операция добавления, тогда вызывается функция ep_insert() для выполнения операция добавления.

Продолжим анализ реализации функции ep_insert() операции добавления:

Язык кода:javascript
копировать
static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{
    ...
    error = -ENOMEM;
    // Подать заявку на один epitem объект
    if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
        goto error_return;

    // инициализация epitem объект
    INIT_LIST_HEAD(&epi->rdllink);
    INIT_LIST_HEAD(&epi->fllink);
    INIT_LIST_HEAD(&epi->pwqlist);
    epi->ep = ep;
    ep_set_ffd(&epi->ffd, tfile, fd);
    epi->event = *event;
    epi->nwait = 0;
    epi->next = EP_UNACTIVE_PTR;

    epq.epi = epi;
    // Эквивалентно: epq.pt->qproc = ep_ptable_queue_proc
    init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

    // вызвать отслеживаемый файл poll интерфейс. 
    // Этот интерфейс состоит из соответствующей файловой системы, нравиться socket из tcp_poll().
    revents = tfile->f_op->poll(tfile, &epq.pt);
    ...
    ep_rbtree_insert(ep, epi); // Пучок epitem объект добавлен вepollizУправление в красно-черных деревьях

    spin_lock_irqsave(&ep->lock, flags);

    // нравиться Если файл контролируется, можно выполнять соответствующие операции чтения и записи.
    // Затем Пучок файла добавить в epoll очередь готовности rdllist середина, И вызов пробуждения epoll_wait() из процесса.
    if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
        list_add_tail(&epi->rdllink, &ep->rdllist);

        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }

    spin_unlock_irqrestore(&ep->lock, flags);
    ...
        return 0;
    ...
    }

Отслеживаемый файл передается epitem Объекты являются управляемыми, а это означает, что отслеживаемые файлы будут инкапсулированы в epitem объект, который затем будет добавлен в eventpoll Объект управление красно-черным деревом (нравиться приведенному выше коду из ep_rbtree_insert(ep, epi))。

tfile->f_op->poll(tfile, &epq.pt) Функция этой строки кода — вызвать отслеживаемый файл. poll() интерфейс, если прослушиваемый файл является socket дескриптор, то он будет вызываться tcp_poll(), давайте посмотрим tcp_poll() Что было сделано:

Язык кода:javascript
копировать
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
    struct sock *sk = sock->sk;
    ...
    poll_wait(file, sk->sk_sleep, wait);
    ...
    return mask;
}

Каждый объект сокета имеет очередь ожидания для хранения процессов, ожидающих изменения статуса сокета.

Из приведенного выше кода мы можем узнать, что tcp_poll() вызывает функцию poll_wait(), а poll_wait() в конечном итоге вызывает функцию ep_ptable_queue_proc(), которая реализована следующим образом:

Язык кода:javascript
копировать
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
{
    struct epitem *epi = ep_item_from_epqueue(pt);
    struct eppoll_entry *pwq;

    if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
        init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
        pwq->whead = whead;
        pwq->base = epi;
        add_wait_queue(whead, &pwq->wait);
        list_add_tail(&pwq->llink, &epi->pwqlist);
        epi->nwait++;
    } else {
        epi->nwait = -1;
    }
}

ep_ptable_queue_proc() Основная задача функции — преобразовать текущий epitem объект добавлен в socket Объект ожидает в очереди и устанавливает функцию пробуждения на ep_poll_callback(), то есть, когда socket При изменении статуса будет инициирован звонок ep_poll_callback() функция.

Функция ep_poll_callback() реализована следующим образом:

Язык кода:javascript
копировать
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
    ...
    // Пучокготовыйиздокументдобавить В очереди готовности
    list_add_tail(&epi->rdllink, &ep->rdllist);

is_linked:
    // Вызов пробуждения epoll_wait() и Заблокированный процесс.
    if (waitqueue_active(&ep->wq))
        wake_up_locked(&ep->wq);
    ...
    return 1;
}

ep_poll_callback() Основная задача функции — добавить готовый дескриптор файла в eventepoll Очередь готовности объектасередина,Затем Вызов пробуждения epoll_wait() Заблокированный процесс.

5. Блокировка и пробуждение процессов

После добавления отслеживаемого файлового дескриптора в epoll вы можете дождаться изменения статуса отслеживаемого файла, вызвав epoll_wait(). Вызов epoll_wait() заблокирует текущий процесс. Когда состояние отслеживаемого файла изменится, вызов epoll_wait() вернет управление.

Прототип системного вызова epoll_wait() выглядит следующим образом:

Язык кода:javascript
копировать
long epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

Описание параметра:

  • epfd: вызов epoll_create() функциясоздаватьизepollручка。
  • события: используется для хранения списка готовых файлов.
  • maxevents: Размер массива событий.
  • таймаут: установите таймаут ожидания.

О возвращаемом значении: В случае успеха epoll_wait() возвращает количество готовых файловых дескрипторов или ноль, если ни один файловый дескриптор не готов в течение запрошенного таймаута в миллисекундах. При возникновении ошибки epoll_wait() возвращает -1 и соответствующим образом устанавливает errno.

Функция epoll_wait() вызовет функцию ядра sys_epoll_wait(), а функция sys_epoll_wait() в конечном итоге вызовет функцию ep_poll(). Давайте посмотрим на реализацию функции ep_poll():

Язык кода:javascript
копировать
static int ep_poll(struct eventpoll *ep, 
    struct epoll_event __user *events, int maxevents, long timeout)
{
    ...
    // нравитьсяфруктыготовыйдокумент Список пуст
    if (list_empty(&ep->rdllist)) {
        // Пучоктекущий процессдобавить вepollизочередь ожиданиясередина
        init_waitqueue_entry(&wait, current);
        wait.flags |= WQ_FLAG_EXCLUSIVE;
        __add_wait_queue(&ep->wq, &wait);

        for (;;) {
            set_current_state(TASK_INTERRUPTIBLE); // Пучоктекущий процессустановлен наспатьсостояние
            if (!list_empty(&ep->rdllist) || !jtimeout) // нравитьсяфрукты有готовыйдокументили тайм-аут, выход из цикла
                break;
            if (signal_pending(current)) { // сигнал полученный тоже хочет бросить
                res = -EINTR;
                break;
            }

            spin_unlock_irqrestore(&ep->lock, flags);
            jtimeout = schedule_timeout(jtimeout); // Откажитесь от процессора, Переключиться на другие процессы для выполнения
            spin_lock_irqsave(&ep->lock, flags);
        }
        // Есть 3 ситуации, когда это произойдет:
        // 1. В отслеживаемой коллекции файлов есть готовые файлы.
        // 2. Тайм-аут был установлен и истек.
        // 3. сигнал получен
        __remove_wait_queue(&ep->wq, &wait);

        set_current_state(TASK_RUNNING);
    }
    /* Естьготовыйиздокумент? */
    eavail = !list_empty(&ep->rdllist);

    spin_unlock_irqrestore(&ep->lock, flags);

    if (!res && eavail 
        && !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
        goto retry;

    return res;
}

Функция ep_poll() в основном выполняет следующие действия:

  1. Определите, есть ли готовые файлы в отслеживаемой коллекции файлов, и сообщите, если есть.
  2. Если нет, добавьте текущий процесс в очередь ожидания epoll и перейдите в режим сна.
  3. Процесс будет находиться в режиме ожидания до тех пор, пока не возникнут следующие условия:
    • В отслеживаемой коллекции файлов есть готовые файлы.
    • Тайм-аут был установлен и истек.
    • сигнал получен
  4. нравитьсяфрукты有готовыйиздокумент,Затемвызов ep_send_events() функция Пучокготовыйдокументкопироватьприезжать events параметры.
  5. Возвращает количество готовых файловых дескрипторов.

6. Резюме

Ниже приводится текстовое описание всего процесса реализации epoll мультиплексирования ввода-вывода:

  1. проходитьвызов epoll_create() Функциисоздавать и инициализировать eventpoll объект.
  2. проходитьвызов epoll_ctl() функция Пучокпод наблюдениемиздескриптор файла (нравиться socket дескриптор файла) упакованный в epitem объектидобавить в eventpoll Объект Управляемые красно-черные деревья.
  3. проходитьвызов epoll_wait() функцияждатьпод наблюдениемиздокументсостояние发生改变。
  4. При изменении статуса отслеживаемого файла (например, socket полученные данные):
    • будет соответствовать дескриптору файла epitem объект добавлен в eventpoll Очередь готовности объекта rdllist середина.и Пучокготовыйочередьиздокументсписоккопироватьприезжать epoll_wait() функцияиз events параметры.
    • Операционная система будет socket Ожидание в очередиизпроцессположить обратноприезжать工作очередь,Процесс становится запущенным,Прямо сейчас Вызов пробуждения epoll_wait() функциязаблокирован(спать)изпроцесс。

Ссылки

epoll_create(2) - Linux manual page - man7.org Ядро LinuxEpoll Принцип реализации Linux source code (v6.0) - Elixir Bootlin Если эта статья не может объяснить суть эполла, то придите и задушите меня!

boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода