многопоточная обработка интелекта. продолжение.

Версия Minecraft
1.7.10
235
3
21
интелект ломания блоков из прошлой темы в многопоточности работает хорошо, ничего не ломается, но после пары реальных тестов, стало ясно, что сервер по прежнему плохо справляется с таким количеством мобов
немного расскажу про своего моба, это по сути сильно переписанный зомби, с новыми ai и переписанными старыми, а также сотней других правок
пойдя дальше, я подумал, ну вот ломание блоков вынес, хорошо, но у зомби то еще с 20 частей ai, которые сумарно гораздо тяжелее моего ломания блоков
в EntityLiving происходит обработка всех алгоритмов, в EntityCreature и EntityMob метод не дополняется
EntityStrong:
protected void updateAITasks()
    {
        ++this.entityAge;
        this.worldObj.theProfiler.startSection("checkDespawn");
        this.despawnEntity();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.startSection("sensing");
        this.senses.clearSensingCache();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.startSection("targetSelector");
        this.targetTasks.onUpdateTasks();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.startSection("goalSelector");
        this.tasks.onUpdateTasks();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.startSection("navigation");
        this.navigator.onUpdateNavigation();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.startSection("mob tick");
        this.updateAITick();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.startSection("controls");
        this.worldObj.theProfiler.startSection("move");
        this.moveHelper.onUpdateMoveHelper();
        this.worldObj.theProfiler.endStartSection("look");
        this.lookHelper.onUpdateLook();
        this.worldObj.theProfiler.endStartSection("jump");
        this.jumpHelper.doJump();
        this.worldObj.theProfiler.endSection();
        this.worldObj.theProfiler.endSection();
    }
посмотрев на это, почему бы просто не вынести это все в обработку на отдельный пул потоков
EntityStrongZombieEngine:
@Override
protected void updateAITasks()
{
    PoolController.instance.addTask(new UpdateAITask(this));
}
UpdateAITask:
public class UpdateAITask implements Runnable
{
    private final Random rand = new Random();
    private EntityStrongZombieEngine updatingEntity;

    public UpdateAITask(EntityStrongZombieEngine updatingEntity)
    {
        this.updatingEntity = updatingEntity;
    }

    @Override
    public void run()
    {
        //updatingEntity.updatings++;
        System.out.println("mark1");
        updatingEntity.entityAge++;
        System.out.println("mark2");
        updatingEntity.publicDespawnEntity();
        System.out.println("mark3");
        updatingEntity.senses.clearSensingCache();
        System.out.println("mark4");
        updatingEntity.targetTasks.onUpdateTasks();
        System.out.println("mark5");
        updatingEntity.tasks.onUpdateTasks();
        System.out.println("mark6");
        updatingEntity.navigator.onUpdateNavigation();
        System.out.println("mark7");
        updatingEntity.publicUpdateAITick();
        System.out.println("mark8");
        updatingEntity.moveHelper.onUpdateMoveHelper();
        System.out.println("mark9");
        updatingEntity.lookHelper.onUpdateLook();
        System.out.println("mark10");
        updatingEntity.jumpHelper.doJump();
        System.out.println("mark11");

        PoolController.instance.finishTask();
    }
}
и все хорошо, в одиночке в среде работает на ура, в связке сервера и одиночки в среде вместе тоже работает, собрал, залил на лаунчер и термос, не работает ни на термосе, ни в одиночке, они просто стоят, из всех System.out.println("mark"); выдается только 1, хотя если заспавнить 1000 мобов, то нагрузка видна на всех ядрах, что значит пул работает
у меня вообще нет идей, мжб кто сможет помочь?
 
Последнее редактирование:
Решение
Вероятно, код падает на строке после твоего принтлна. ThreadPoolExecutor грешит тем, что исключения, возникшие во время выполнения тасков он просто выбрасывает, обработку всех ошибок ты должен реализовать самостоятельно.
Создай свой ThreadPoolExecutor и реализуй afterExecute (вроде был способ проще, но я сейчас не вспомню):
235
3
21
updatingEntity.publicDespawnEntity() и updatingEntity.publicUpdateAITick() это просто паблик методы, которые вызывают аналогичные protected

UPD1: протестил просто собраный мод на голом forge в tlauncher без других модов - тоже самое
 
Последнее редактирование:
1,990
18
105
Вероятно, код падает на строке после твоего принтлна. ThreadPoolExecutor грешит тем, что исключения, возникшие во время выполнения тасков он просто выбрасывает, обработку всех ошибок ты должен реализовать самостоятельно.
Создай свой ThreadPoolExecutor и реализуй afterExecute (вроде был способ проще, но я сейчас не вспомню):
 
235
3
21
спасибо большое
[00:13:26] [pool-28-thread-1/INFO] [/]: java.lang.IllegalAccessError: tried to access field net.minecraft.entity.EntityLivingBase.field_70708_bq from class ru.Pa4ok.mod.entity.zombie.concurent.UpdateAITask

тут был глупый вопрос, теперь его нет
 
Последнее редактирование:
235
3
21
оглашу небольшой итог
все работает отлично
вчера на тесте до этой идеи было заспавнено 800 мобов в 1 чанке, тпс стал 10
сейчас я заспавнил 1000, тпс 20, нагрузка на главный поток 95%, но вчера ядра были лучше по частоте
чтобы опустить тпс до уровня первого теста потребовалось 1400 мобов, так что если забыть про разницу частот, то выигрыш довольно большой
так что, оно того стоило
сейчас думаю о том чтобы пойти выше и попробовать разгрузить все onUpdate и onLivingUpdate
 
Последнее редактирование:
2,505
81
397
Ух, страшные вещи делаешь..
Во-первых, твой моб взаимодействует с внешним миром, который ни разу не thread safe. Если чтение его данных ещё хоть как-то может быть допустимо, то модификация это тупо боль.
Во-вторых, внутри задачи модифицируешь non thread safe данные (как минимум, поля родительских классов). Опять же из-за race condition может произойти чо угодно.
В-третьих, никак не синхронизируешь задачи. При большом количестве мобов задачи могут забивать список и таким образом неявно выполняться отложенно. Ещё хуже - выполняться, когда моб уже убит. Совсем плохо - выполняться одновременно для одного и того же моба.
В-четвёртых, икремент не атомарная операция.

Если ты немного потестил и багов не возникло, это не значит, что их не будет. Особенно, когда даже не думаешь о гонке потоков. Сойдутся звёзды и все пойдёт по *изде.
 
Можно вообще не парится со всякими вашими атомарносятими и тредсефами, тем более что в реалиях майна это очень затратно. Просто создать тредпул, в апдейттике энтити сабмитить туда чистые таски - получать футуры, в этом же апдейте чекать их на выполнение - результат вычисления поведения моба, и уже там взаимодействовать с non thread safe майном.
 
235
3
21
При большом количестве мобов задачи могут забивать список и таким образом неявно выполняться отложенно.
для этого у меня уже есть система контроля

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

модифицируешь non thread safe данные (как минимум, поля родительских классов)
пересобрать термос и расставить volatile к переменным, заменить ArrayList на лист из concurrent пакета и расставить syncronized в паре функций не сложно
 
Последнее редактирование:
235
3
21
235
3
21
Можно вообще не парится со всякими вашими атомарносятими и тредсефами, тем более что в реалиях майна это очень затратно. Просто создать тредпул, в апдейттике энтити сабмитить туда чистые таски - получать футуры, в этом же апдейте чекать их на выполнение - результат вычисления поведения моба, и уже там взаимодействовать с non thread safe майном.
не очень понял сути, ждать в апдейте энтити, пока таск выполнится? а зачем тогда вообще выносить в отдельные потоки? или я не так понял?
 
2,505
81
397
при не перегруженной работе гоняться не за чем будет
Гоняться будут основной с воркером(ами). И это будет всегда, а не при перегруженной работе. Если ты этого не понимаешь, то ты зря сунулся в многопоточность.

Советую, всё-таки, затестить при каком количестве мобов очередь тасков станет постоянно увеличиваться.
 
235
3
21
уже тестил, железо - 3 ядра по 3ггц по 1 потоку в каждом, кол-во потоков в пуле - 2, третий основной для майна
гораздо быстрее забивается основной и падает тпс, а когда он падает, то уменьшается кол-во тасков для пула
 
235
3
21
Ждать это не тоже самое что проверять. Проверка лишь одно условие в тике энтити, не больше. А ждать да, бессмысленно
ну вообще нврн можно сделать очереди внутри каждой энтити и уже их обрабатывать потоками и потом в них же пихать резултат
 
7,099
324
1,510
Сверху